Validation & Exception Handling
Bean Validation with @Valid and Jakarta Validation Constraints
Applying @NotNull, @Size, @Email, and triggering validations on controllers inputs.
Introduction
Bean Validation is a standard validation framework for Java (Jakarta Bean Validation, formerly JSR 380) that allows developers to declare validation rules directly on domain model/DTO fields using annotations. Instead of pollution by imperative if/else checks throughout controllers or service logic, constraints are declared declaratively and triggered at the entry point of the API.
Why It Matters
Invalid incoming data is a primary source of security bugs, data corruption, and crashes. Ensuring input matches expected constraints (e.g. valid format, within range, non-empty values) at the outermost layer guarantees the application code works with clean, well-formed state.
Real-World Analogy
Think of validation constraints like airport security. Before passengers (requests) can enter the gates (business logic), they go through a standardized screening checkpoint (Jakarta Validation constraints). If their baggage or credentials do not meet requirements, security turns them back immediately, rather than letting flight attendants deal with violations on board.
How It Works
When a controller method parameter is annotated with @Valid (from Jakarta) or @Validated (Spring-specific for groups/method-level), Spring's RequestResponseBodyMethodProcessor triggers the underlying Validator (usually Hibernate Validator, the reference implementation of Jakarta Bean Validation) to run before the controller action starts.
If validation fails, a MethodArgumentNotValidException is thrown. This exception contains details about every constraint violation (e.g., target fields, invalid values, default messages), which is then mapped into a clean response format.
Common Jakarta Validation Annotations
| Annotation | Purpose | Applies To |
|---|---|---|
@NotNull |
Checks if the value is not null (allows empty strings/collections). | Any Type |
@NotEmpty |
Checks if the value is not null and its size/length is greater than 0. | Strings, Collections, Arrays |
@NotBlank |
Checks if the string is not null and contains at least one non-whitespace character. | Strings only |
@Size(min=x, max=y) |
Ensures the element's size/length is between min and max inclusive. | Strings, Collections, Arrays |
@Email |
Validates that the string complies with a valid email format pattern. | Strings |
@Min / @Max |
Ensures value is numeric and is greater/smaller than specified boundaries. | Numeric Types |
Practical Example
Here is how to apply validation to a DTO and trigger it inside a Controller:
Next, use @Valid in the controller to trigger validation on incoming JSON request bodies:
Quick Quiz
Q1: Which annotation is used inside the Controller method signature to trigger validation on an incoming body?
A) @ValidatedOnly
B) @Valid
C) @Verify
D) @CheckConstraints
Answer: B — @Valid triggers the Jakarta validation framework before parameters are bound.
Q2: How does @NotBlank differ from @NotEmpty?
A) @NotBlank allows whitespace-only strings
B) @NotBlank ensures the string is not null and has at least one non-whitespace character
C) @NotEmpty checks only for null references
D) They are completely identical
Answer: B — @NotEmpty checks length > 0, so " " (spaces) passes @NotEmpty but fails @NotBlank.
Scenario-Based Challenge
Production Scenario:
You have a DTO containing a property representing a phone number: private String phoneNumber;. You want to make sure it is optional (nullable), but if it *is* provided, it must be between 7 and 15 digits. How do you declare these validation rules?
You can combine @Size(min = 7, max = 15) without applying @NotNull or @NotBlank. In Jakarta Validation, constraint validators (except for things like @NotNull) treat null values as valid. Therefore, if the value is null, it passes. If it is provided, it must satisfy the @Size constraint. Alternatively, you can use a regular expression constraint: @Pattern(regexp = "^[0-9]{7,15}$"), which also considers null to be valid.
Interview Questions
1. What exception is thrown when parameter validation fails in a Spring controller, and how is it caught?
When validating @RequestBody, a MethodArgumentNotValidException is thrown. For path parameters or request parameters validated at method levels (using @Validated on the class), a ConstraintViolationException is thrown. These exceptions can be caught globally using a @ControllerAdvice class containing @ExceptionHandler methods.
2. What is the difference between @Valid and @Validated in Spring?
@Valid is standard Jakarta specification, used for single-level parameter validation (e.g., body parameters, nested properties). @Validated is a Spring-specific annotation that supports validation "groups" (validating different subsets of fields under different scenarios) and is required on class targets to trigger validation on parameters like @PathVariable or @RequestParam.
Production Considerations
Avoid putting heavy resource-bound rules (like checking DB uniqueness) directly in Hibernate validation annotations to keep validations fast. Instead, perform simple syntactical validation at the DTO layer, and execute DB checks in the service layer where transactions are managed properly.