spring-validation

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-validation for comprehensive documentation.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "spring-validation" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-spring-validation

Spring Validation

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-validation for comprehensive documentation.

Jakarta vs Javax Namespace

Spring Boot 3.x / Spring 6.x uses Jakarta EE 10 with jakarta.validation package:

// Spring Boot 3.x - USE THIS import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Email;

// Spring Boot 2.x - OLD (do not use with Boot 3) // import javax.validation.Valid; // import javax.validation.constraints.NotBlank;

Spring Boot Jakarta EE Package

3.x+ 10 jakarta.validation.*

2.x 8 javax.validation.*

Request DTO Validation

@Data public class CreateUserRequest {

@NotBlank(message = "Name is required")
@Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters")
private String name;

@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
private String email;

@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
@Pattern(
    regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
    message = "Password must contain uppercase, lowercase, and number"
)
private String password;

@NotNull(message = "Birth date is required")
@Past(message = "Birth date must be in the past")
private LocalDate birthDate;

@NotNull(message = "Role is required")
private UserRole role;

@Min(value = 0, message = "Age must be positive")
@Max(value = 150, message = "Age must be less than 150")
private Integer age;

@DecimalMin(value = "0.0", message = "Salary must be positive")
private BigDecimal salary;

}

Controller with Validation

@RestController @RequestMapping("/api/v1/users") @RequiredArgsConstructor public class UserController {

@PostMapping
public ResponseEntity<UserResponse> create(@Valid @RequestBody CreateUserRequest dto) {
    return ResponseEntity.status(HttpStatus.CREATED)
        .body(userService.create(dto));
}

@PutMapping("/{id}")
public ResponseEntity<UserResponse> update(
        @PathVariable @Positive Long id,
        @Valid @RequestBody UpdateUserRequest dto) {
    return ResponseEntity.ok(userService.update(id, dto));
}

// Validate query params
@GetMapping
public ResponseEntity<Page<UserResponse>> findAll(
        @RequestParam @Min(0) int page,
        @RequestParam @Min(1) @Max(100) int size) {
    return ResponseEntity.ok(userService.findAll(page, size));
}

}

Custom Validator

// Annotation @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UniqueEmailValidator.class) @Documented public @interface UniqueEmail { String message() default "Email already registered"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }

// Validator @Component @RequiredArgsConstructor public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {

private final UserRepository userRepository;

@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
    if (email == null) return true; // @NotNull handles this
    return !userRepository.existsByEmail(email);
}

}

// Usage @Data public class RegisterRequest { @NotBlank @Email @UniqueEmail private String email; }

Cross-Field Validation

// Annotation @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PasswordMatchValidator.class) public @interface PasswordMatch { String message() default "Passwords do not match"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }

// Validator public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, PasswordChangeRequest> {

@Override
public boolean isValid(PasswordChangeRequest dto, ConstraintValidatorContext context) {
    if (dto.getNewPassword() == null || dto.getConfirmPassword() == null) {
        return true;
    }
    return dto.getNewPassword().equals(dto.getConfirmPassword());
}

}

// Usage @Data @PasswordMatch public class PasswordChangeRequest { @NotBlank private String currentPassword;

@NotBlank
@Size(min = 8)
private String newPassword;

@NotBlank
private String confirmPassword;

}

Validation Groups

// Group interfaces public interface OnCreate {} public interface OnUpdate {}

// DTO with groups @Data public class UserRequest {

@Null(groups = OnCreate.class, message = "ID must be null on create")
@NotNull(groups = OnUpdate.class, message = "ID is required on update")
private Long id;

@NotBlank(groups = {OnCreate.class, OnUpdate.class})
private String name;

@NotBlank(groups = OnCreate.class)
@Null(groups = OnUpdate.class, message = "Email cannot be changed")
private String email;

}

// Controller @PostMapping public ResponseEntity<UserResponse> create( @Validated(OnCreate.class) @RequestBody UserRequest dto) { return ResponseEntity.status(HttpStatus.CREATED).body(userService.create(dto)); }

@PutMapping("/{id}") public ResponseEntity<UserResponse> update( @PathVariable Long id, @Validated(OnUpdate.class) @RequestBody UserRequest dto) { return ResponseEntity.ok(userService.update(id, dto)); }

Global Exception Handler

@RestControllerAdvice @Slf4j public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity&#x3C;ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
    Map&#x3C;String, String> errors = new HashMap&#x3C;>();
    ex.getBindingResult().getFieldErrors().forEach(error ->
        errors.put(error.getField(), error.getDefaultMessage())
    );
    return ResponseEntity.badRequest()
        .body(ErrorResponse.builder()
            .message("Validation failed")
            .errors(errors)
            .timestamp(LocalDateTime.now())
            .build());
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity&#x3C;ErrorResponse> handleConstraint(ConstraintViolationException ex) {
    Map&#x3C;String, String> errors = new HashMap&#x3C;>();
    ex.getConstraintViolations().forEach(violation -> {
        String field = violation.getPropertyPath().toString();
        errors.put(field, violation.getMessage());
    });
    return ResponseEntity.badRequest()
        .body(ErrorResponse.builder()
            .message("Validation failed")
            .errors(errors)
            .timestamp(LocalDateTime.now())
            .build());
}

// Spring 6.1+ / Spring Boot 3.2+ - Method parameter validation
@ExceptionHandler(HandlerMethodValidationException.class)
public ResponseEntity&#x3C;ErrorResponse> handleMethodValidation(HandlerMethodValidationException ex) {
    Map&#x3C;String, String> errors = new HashMap&#x3C;>();
    ex.getAllValidationResults().forEach(result -> {
        result.getResolvableErrors().forEach(error -> {
            String field = error.getCodes() != null &#x26;&#x26; error.getCodes().length > 0
                ? error.getCodes()[0] : "unknown";
            errors.put(field, error.getDefaultMessage());
        });
    });
    return ResponseEntity.badRequest()
        .body(ErrorResponse.builder()
            .message("Validation failed")
            .errors(errors)
            .timestamp(LocalDateTime.now())
            .build());
}

}

@Data @Builder public class ErrorResponse { private String message; private Map<String, String> errors; private LocalDateTime timestamp; }

Common Validation Annotations

Annotation Purpose

@NotNull

Not null

@NotBlank

Not null/empty/whitespace (String)

@NotEmpty

Not null/empty (Collection, String)

@Size

Size constraints

@Min / @Max

Numeric range

@Email

Email format

@Pattern

Regex pattern

@Past / @Future

Date constraints

@Positive / @Negative

Number sign

@Valid

Cascade validation

@Validated

With groups

Best Practices

Do Don't

Use @Valid on @RequestBody Skip validation on endpoints

Create custom validators for domain rules Put regex in multiple places

Use validation groups for context Create separate DTOs for each operation

Return structured error responses Return raw exception messages

Validate early at API boundary Validate deep in service layer

When NOT to Use This Skill

  • Business logic validation - Use service layer with custom exceptions

  • Security checks - Use Spring Security annotations

  • Database constraints - Use JPA constraints additionally

  • External data validation - Validate after mapping

Anti-Patterns

Anti-Pattern Problem Solution

Validation in service layer Duplicated validation Use @Valid on controller

Missing @Valid annotation Validation bypassed Always add @Valid

Generic error messages Poor UX Use specific message attributes

Business logic in validators Tight coupling Keep validators simple

No global exception handler Inconsistent errors Add @ControllerAdvice

Quick Troubleshooting

Problem Diagnostic Fix

Validation not triggered Check @Valid presence Add @Valid to parameter

Custom validator not called Check @Constraint annotation Verify validatedBy class

Groups not working Check @Validated Use @Validated not @Valid

Nested object not validated Check @Valid on field Add @Valid to nested field

ConstraintViolationException Path params/query Add @Validated on controller class

Reference Documentation

  • Bean Validation Reference

  • Spring Validation

  • Hibernate Validator

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

webrtc

No summary provided by upstream source.

Repository SourceNeeds Review