Validations in Spring Framework

What is Validation?

Validation is the process of checking whether the input data is correct, complete, and meets certain rules or constraints before it's processed or saved.

In Java, and especially in Spring applications, validation ensures that:

  • A field is not null when it must have a value.

  • A string meets minimum or maximum length.

  • A number is within a certain range.

  • An email address is properly formatted.

  • Custom business rules (like "age must be above 18") are enforced.

Validation is usually applied to:

  • Data received from users (form submissions, JSON payloads).

  • Objects used in APIs (DTOs).

  • Data models before saving them to the database.

Example of a simple constraint:

public class User {
    @NotBlank
    private String username;

    @Email
    private String email;
}

This example says: username should not be blank, and email must be in proper email format.

Why is Validation Important in Spring Applications?

Spring applications commonly deal with:

  • User input via APIs

  • Form data from websites

  • External data (like messages, files)

If data is not validated:

  • Invalid or incomplete data may reach your business logic or database.

  • It can cause runtime exceptions, data corruption, or security vulnerabilities.

  • It becomes harder to debug or trace what went wrong.

By validating early (at the controller level), we:

  • Prevent bad data from entering your system.

  • Return useful and specific error messages to the user.

  • Keep your core logic clean and focused on business rules.

Spring makes this process easy by integrating with Jakarta Bean Validation (like @NotNull, @Size, etc.) and automatically triggering checks.

Difference between Manual & Automatic (Declarative) Validation

Manual Validation

In manual validation, we write the code to check values and handle errors ourself.

Example:

if (user.getUsername() == null || user.getUsername().isEmpty()) {
    throw new IllegalArgumentException("Username is required");
}

This approach:

  • Requires more code

  • Is error-prone

  • Needs to be duplicated if used in many places

  • Offers no built-in grouping or nesting support

Automatic (Declarative) Validation

Spring uses declarative validation with annotations like @NotNull, @Size, @Valid, etc.

We annotate the class fields, and Spring + Jakarta Validation will automatically check them.

Example:

public class User {
    @NotBlank(message = "Username is required")
    private String username;

    @Email(message = "Invalid email")
    private String email;
}

Then in the controller:

@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody User user) {
    // If validation fails, Spring throws MethodArgumentNotValidException
    return ResponseEntity.ok("Registered");
}

We don't write if-checks — the framework handles it.

Validation Types in Spring

Spring supports multiple ways of performing validation. Depending on the use case and complexity, we can choose:

  1. Bean Validation (Jakarta Validation API – jakarta.validation.*)

  2. Spring’s own Validator interface (org.springframework.validation.Validator)

  3. Custom Validation (annotations + logic)

  4. Programmatic vs Declarative Validation

1. Bean Validation (Jakarta Validation API)

This is the most common and recommended approach in Spring applications.

  • Uses annotations from jakarta.validation (previously javax.validation) like @NotNull, @Size, @Email, @Pattern, etc.

  • Spring integrates with Jakarta Validation and performs the checks automatically.

  • Used mostly in:

    • DTOs for REST APIs

    • Form submissions

    • Method parameters

Example:

public class UserDTO {
    @NotBlank
    private String name;

    @Email
    private String email;
}

In the controller:

@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody UserDTO dto) {
    // If invalid, Spring returns 400 with error details
}
  • Validation is recursive — if UserDTO contains another object with annotations, it is also validated using @Valid.

2. Spring’s Validator Interface

  • This is Spring’s native validation mechanism before it adopted Jakarta Bean Validation.

  • We implement the interface org.springframework.validation.Validator and manually define validation logic.

  • Often used for:

    • Complex business validations that can't be captured with simple annotations

    • Older Spring applications

    • Manual validation within services/controllers

Example:

public class UserValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return User.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        User user = (User) target;

        if (user.getName() == null || user.getName().length() < 3) {
            errors.rejectValue("name", "name.tooShort", "Name must be at least 3 characters");
        }
    }
}

In controller:

@Autowired
private UserValidator userValidator;

@PostMapping("/users")
public String submitForm(@ModelAttribute User user, BindingResult result) {
    userValidator.validate(user, result);
    if (result.hasErrors()) {
        return "form";
    }
    return "success";
}

This approach gives us full control but requires more boilerplate.

3. Custom Validation

This includes custom annotations and validation logic, usually built on top of Bean Validation.

Use when:

  • Built-in annotations aren’t sufficient (e.g., checking that a username is unique in the DB)

  • You want reusable annotations for business rules

Steps:

  1. Create an annotation

  2. Create a validator that implements ConstraintValidator

Example:

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
public @interface UniqueUsername {
    String message() default "Username already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Validator:

public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        // Call repository to check if username exists
        return !userRepository.existsByUsername(username);
    }
}

Use in DTO:

@UniqueUsername
private String username;

4. Programmatic vs Declarative Validation

Declarative Validation:

  • Uses annotations like @NotNull, @Valid, etc.

  • Cleaner and less code

  • Handled automatically by the framework

Example:

@Size(min = 3)
private String username;

Spring automatically checks this and returns a 400 response if invalid.

Programmatic Validation:

  • Manual checks in Java code using Validator or if conditions

  • Offers full control

  • More verbose

Example:

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(user);

We inspect violations and act accordingly.

When to use:

  • Use declarative for most use-cases (cleaner and standard).

  • Use programmatic only when:

    • We need dynamic constraints

    • We're writing libraries

    • We're validating inside services instead of controllers

Comparison

1. Bean Validation (Jakarta Validation API – jakarta.validation.*)

Aspect
Description

What it is

Standard Java validation via annotations (JSR-380 / Bean Validation 2.0).

Provided by

jakarta.validation (formerly javax.validation).

Annotations used

@NotNull, @Size, @Email, @Min, @Pattern, etc.

Where it's used

DTOs, Entities, Request bodies.

Spring Integration

Deeply integrated — works automatically with @Valid / @Validated in controllers.

Custom groups

Yes, supports groups.

Nested validation

Yes, with @Valid.

Best for

Common, field-level validation (input validation, simple rules).

Most commonly used in modern Spring apps.

2. Spring’s Validator Interface (org.springframework.validation.Validator)

Aspect
Description

What it is

Spring-specific interface for imperative, programmatic validation.

Method

Must implement validate(Object, Errors) method.

No annotations

Logic is written in Java code, not annotations.

Use case

When you need full control over validation logic or want to validate objects not annotated.

Registration

Must be registered manually or via @InitBinder.

Groups support

Manual.

Drawback

Verbose and manual.

Best when:

  • You want logic-based validation

  • You're validating a non-annotated object

  • You need complex cross-field logic

3. Custom Validation (Annotations + Logic)

Aspect
Description

What it is

User-defined annotation + corresponding ConstraintValidator.

Example

@ValidPassword, @ValidUsername, etc.

How

Define an annotation → implement validator → apply it to fields.

Powerful

Can include external service calls, database checks.

Reusable

Yes — once defined, works like any built-in annotation.

Spring aware

If needed, validator can be made Spring-aware via @Autowired.

Best when:

  • You have domain-specific rules (e.g., username must be unique)

  • Need reusability across projects or modules

4. Programmatic vs Declarative Validation

Comparison
Programmatic
Declarative

How

Call validator manually in code

Use annotations on fields and classes

Example

validator.validate(object)

@NotBlank, @Valid, @Size, etc.

Flexible?

Yes – full control

Less flexible, but cleaner

Verbose?

Yes

No – concise

When to use

For conditional logic, external checks

For field-level rules

Declarative validation is ideal for most cases. Use programmatic when logic is dynamic, conditional, or involves services.

When to Use What ?

Use Case
Recommended Validation Type

Simple form field checks

Bean Validation (Jakarta)

Request DTO validation in controller

Jakarta + @Valid / @Validated

Cross-field or logic-based validation

Spring Validator OR Custom ConstraintValidator

Need reusability and domain-specific rule

Custom Annotation + Validator

Complex condition-based logic

Programmatic validation

Different rules for create/update/view actions

Validation Groups + @Validated

Validation Groups

What are Validation Groups?

Validation groups allow us to apply different sets of constraints depending on the context.

For example:

  • When creating a user, we might want to validate fields like username, email, and password.

  • When updating, we might only want to validate email (not password or username).

Instead of duplicating your DTOs or writing custom logic, we can define groups and apply them using the groups attribute on annotations.

Purpose of Validation Groups

  • Context-aware validation — apply only relevant constraints based on the use case (e.g., create vs update).

  • Reusability — same DTO with different validation behavior depending on operation.

  • Cleaner code — avoids manual condition-based checks.

How to Define and Use Validation Groups

1. Define Group Interfaces

public interface Create {}
public interface Update {}

These are just marker interfaces — no methods inside.

2. Annotate Fields with Group Info

Use the groups parameter on constraints to specify where they should apply:

public class UserDTO {

    @NotNull(groups = Update.class)
    private Long id;

    @NotBlank(groups = Create.class)
    private String username;

    @NotBlank(groups = {Create.class, Update.class})
    private String email;
}

Explanation:

  • id must be present only during update

  • username must be provided only during create

  • email must be valid in both cases

3. Trigger Group-Specific Validation in Spring

In Spring MVC, use @Validated instead of @Valid to specify the group:

@PostMapping("/users")
public ResponseEntity<?> createUser(@Validated(Create.class) @RequestBody UserDTO user) {
    // Validates only fields with Create group
}
@PutMapping("/users")
public ResponseEntity<?> updateUser(@Validated(Update.class) @RequestBody UserDTO user) {
    // Validates only fields with Update group
}

Last updated

Was this helpful?