Skip to content

nhatcoi/validation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spring Boot Validation Practice Project with Lombok

This project demonstrates comprehensive validation features in Java and Spring Boot, including Bean Validation (JSR-303/JSR-380), Spring Boot validation, custom validators, validation groups, and Lombok integration for reduced boilerplate code.

Table of Contents

  1. Project Structure
  2. Validation Features
  3. Lombok Integration
  4. Built-in Validation Annotations
  5. Custom Validation
  6. Validation Groups
  7. Testing the Application
  8. Best Practices

Project Structure

validation/
├── src/main/java/com/noahcoy/validation/
│   ├── ValidationApplication.java          # Main Spring Boot application
│   ├── annotation/
│   │   └── ValidUsername.java              # Custom validation annotation
│   ├── controller/
│   │   ├── UserController.java             # REST endpoints with validation
│   │   ├── ValidationDemoController.java   # Demo endpoints
│   │   └── LombokDemoController.java       # Lombok demonstration endpoints
│   ├── exception/
│   │   └── GlobalExceptionHandler.java     # Global validation error handling
│   ├── group/
│   │   └── ValidationGroups.java           # Validation group interfaces
│   ├── model/
│   │   ├── User.java                       # Basic model with Lombok
│   │   ├── UserWithGroups.java             # Model with validation groups + Lombok
│   │   ├── Address.java                    # Address model with Lombok features
│   │   └── UserWithAddress.java            # Nested validation with Lombok
│   ├── service/
│   │   └── ValidationService.java          # Programmatic validation
│   └── validator/
│       └── UsernameValidator.java          # Custom validator implementation
├── pom.xml                                 # Maven dependencies (includes Lombok)
└── README.md                              # This file

Validation Features

1. Bean Validation (JSR-303/JSR-380)

Standard Java validation annotations for common validation scenarios.

2. Spring Boot Validation

  • Automatic validation with @Valid
  • Method-level validation with @Validated
  • Global error handling

3. Custom Validators

  • Custom annotation: @ValidUsername
  • Complex business rule validation
  • Contextual error messages

4. Validation Groups

  • Different validation rules for different operations
  • Create, Update, Admin, and Basic groups
  • Conditional validation based on context

5. Programmatic Validation

  • Manual validation using Validator interface
  • Property-specific validation
  • Value validation without object instances

Lombok Integration

This project extensively uses Lombok to reduce boilerplate code while maintaining full validation functionality.

Key Lombok Annotations Used

@Data

Generates getters, setters, toString, equals, and hashCode methods:

@Data
public class User {
    @NotBlank(message = "Username cannot be blank")
    private String username;
    // No need for getters/setters - Lombok generates them!
}

@Builder

Provides fluent builder pattern for object creation:

@Builder
public class User {
    // Fields with validation annotations
}

// Usage:
User user = User.builder()
    .username("johnsmith")
    .email("john@example.com")
    .age(25)
    .build();

@NoArgsConstructor and @AllArgsConstructor

Generates constructors automatically:

@NoArgsConstructor  // Empty constructor
@AllArgsConstructor // Constructor with all fields
public class User {
    // Fields
}

Advanced Lombok Features

Custom toString exclusion (for security):

@ToString(exclude = {"password"}) // Password won't appear in toString()
public class User {
    private String password;
}

Custom equals/hashCode logic:

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Address {
    @EqualsAndHashCode.Include
    private Long id; // Only ID used for equals/hashCode
    
    private String street; // Not included in equals/hashCode
}

Custom getter/setter access levels:

@Getter(AccessLevel.NONE) // No getter generated
@Setter(AccessLevel.PROTECTED) // Protected setter
private String internalNotes;

Builder defaults:

@Builder.Default
private LocalDateTime registrationDate = LocalDateTime.now();

Lombok + Validation Benefits

  1. Reduced Code: 60-80% less boilerplate code
  2. Validation Preserved: All validation annotations work seamlessly
  3. Builder Pattern: Clean object creation with validation
  4. Security: Easy exclusion of sensitive fields from toString()
  5. Maintainability: Changes to fields automatically update generated methods

Built-in Validation Annotations

Annotation Purpose Example
@NotNull Field must not be null @NotNull private Long id;
@NotEmpty Collection/array must not be null or empty @NotEmpty private List<String> items;
@NotBlank String must not be null, empty, or whitespace @NotBlank private String username;
@Size(min, max) Size must be within bounds @Size(min=3, max=20) private String name;
@Min(value) Number must be >= value @Min(18) private Integer age;
@Max(value) Number must be <= value @Max(120) private Integer age;
@DecimalMin(value) Decimal must be >= value @DecimalMin("0.0") private Double salary;
@DecimalMax(value) Decimal must be <= value @DecimalMax("999999.99") private Double salary;
@Pattern(regexp) String must match regex @Pattern(regexp="^[a-zA-Z0-9_]+$") private String username;
@Email String must be valid email @Email private String email;
@Past Date must be in the past @Past private LocalDate birthDate;
@Future Date must be in the future @Future private LocalDate expiryDate;
@Positive Number must be positive @Positive private Integer count;
@Negative Number must be negative @Negative private Integer deficit;

Custom Validation

Creating Custom Annotation

@Documented
@Constraint(validatedBy = UsernameValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidUsername {
    String message() default "Username must be unique and follow the required format";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Implementing Custom Validator

public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {
    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        // Custom validation logic
        // Return true if valid, false if invalid
    }
}

Validation Groups

Validation groups allow different validation rules for different scenarios:

public class ValidationGroups {
    public interface Create {}    // For user creation
    public interface Update {}    // For user updates
    public interface Admin {}     // For admin operations
    public interface Basic {}     // For basic validation
}

Usage in controller:

@PostMapping("/with-groups")
public ResponseEntity<String> createUser(
    @Validated({ValidationGroups.Create.class, ValidationGroups.Basic.class}) 
    @RequestBody UserWithGroups user) {
    // Only validates Create and Basic groups
}

Testing the Application

Starting the Application

cd validation
./mvnw spring-boot:run

Testing Endpoints

1. Basic Validation Test

# Valid user
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "id": 1,
    "username": "johndoe",
    "email": "john@example.com",
    "password": "Password123",
    "age": 25
  }'

# Invalid user (multiple validation errors)
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "id": 0,
    "username": "ab",
    "email": "invalid-email",
    "password": "weak",
    "age": 15
  }'

2. Lombok Builder Demo

# Demonstrate Lombok builder pattern
curl -X POST http://localhost:8080/api/lombok-demo/builder-demo

3. Nested Validation with Lombok

# Create user with nested address validation
curl -X POST http://localhost:8080/api/lombok-demo/with-address \
  -H "Content-Type: application/json" \
  -d '{
    "username": "janesmith",
    "email": "jane@example.com",
    "password": "SecurePass123",
    "age": 28,
    "primaryAddress": {
      "id": 1,
      "street": "123 Main St",
      "city": "New York",
      "state": "NY",
      "zipCode": "10001",
      "country": "US",
      "verified": true
    }
  }'

4. Lombok Equals Demo

# Demonstrate Lombok equals and hashCode
curl -X GET http://localhost:8080/api/lombok-demo/equals-demo

5. Lombok Features Information

# Get information about Lombok features
curl -X GET http://localhost:8080/api/lombok-demo/lombok-features

6. Validation Groups Test

# Create with groups (missing password - should fail)
curl -X POST http://localhost:8080/api/users/with-groups \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "email": "test@example.com",
    "age": 25
  }'

# Update with groups (password not required)
curl -X PUT http://localhost:8080/api/users/with-groups/1 \
  -H "Content-Type: application/json" \
  -d '{
    "username": "updateduser",
    "email": "updated@example.com",
    "age": 26
  }'

7. Path Variable Validation Test

# Invalid ID (should return validation error)
curl -X GET http://localhost:8080/api/users/0

# Valid ID
curl -X GET http://localhost:8080/api/users/1

8. Request Parameter Validation Test

# Empty username (should return validation error)
curl -X GET "http://localhost:8080/api/users/search?username="

# Valid username
curl -X GET "http://localhost:8080/api/users/search?username=john"

9. Method-Level Validation Test

# Invalid parameters
curl -X POST "http://localhost:8080/api/users/validate-method?name=&age=15"

# Valid parameters
curl -X POST "http://localhost:8080/api/users/validate-method?name=John&age=25"

10. Custom Validation Test

# Reserved username (should fail custom validation)
curl -X POST http://localhost:8080/api/users/with-groups \
  -H "Content-Type: application/json" \
  -d '{
    "username": "admin",
    "email": "admin@example.com",
    "password": "Password123",
    "age": 25
  }'

11. Programmatic Validation Demo

# Run validation demonstrations
curl -X GET http://localhost:8080/api/validation-demo/demonstrate

# Get validation features information
curl -X GET http://localhost:8080/api/validation-demo/features

Expected Error Response Format

When validation fails, the application returns structured error responses:

{
  "timestamp": "2024-01-15T10:30:00",
  "status": 400,
  "error": "Validation Failed",
  "message": "Request contains invalid data",
  "errors": [
    {
      "field": "username",
      "message": "Username must be between 3 and 20 characters",
      "rejectedValue": "ab"
    },
    {
      "field": "email",
      "message": "Email should be valid",
      "rejectedValue": "invalid-email"
    }
  ],
  "path": "Validation Error"
}

Best Practices

  1. Use Appropriate Annotations: Choose the right validation annotation for each field type
  2. Clear Error Messages: Provide user-friendly error messages
  3. Validation Groups: Use groups for different scenarios (create, update, admin)
  4. Custom Validators: Create custom validators for complex business rules
  5. Global Error Handling: Use @ControllerAdvice for consistent error responses
  6. Multi-Layer Validation: Validate at multiple layers (client, server, database)
  7. Nested Validation: Use @Valid for nested object validation
  8. Separation of Concerns: Keep validation logic separate from business logic
  9. Lombok Best Practices:
    • Use @Data for simple DTOs and entities
    • Exclude sensitive fields from toString using @ToString(exclude = {...})
    • Use @EqualsAndHashCode.Include for custom equality logic
    • Combine with validation annotations for robust models
    • Use @Builder for complex object creation
    • Consider @Value for immutable objects

Dependencies

The project uses the following key dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

Additional Resources

Learning Exercises

  1. Add more custom validators (e.g., phone number format, credit card validation)
  2. Implement conditional validation (validation depends on other field values)
  3. Add internationalization (i18n) for validation messages
  4. Create validation for file uploads
  5. Implement cross-field validation (password confirmation)
  6. Add validation for nested objects and collections
  7. Lombok Exercises:
    • Create an immutable model using @Value
    • Implement a custom builder with validation
    • Use @With for creating modified copies of immutable objects
    • Experiment with @Accessors for fluent setters

About

Demonstrates comprehensive validation techniques in Java and Spring Boot

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages