Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 39

Bean Validation Basics

Recommended resources

Hello! Welcome back to your interview preparation course.

In our last lesson, we dove deep into the Spring IoC container to understand and solve circular dependencies. We saw how Spring manages the bean lifecycle, even in complex scenarios. Today, we shift our focus from the internal workings of the container to a critical aspect of API development: ensuring data integrity at the application's edge.

Your learning outcome for this lesson is to implement request validation using Bean Validation annotations. Building robust, secure, and predictable APIs is a core expectation for senior engineers, and having a solid validation strategy is non-negotiable. This topic is highly practical and frequently comes up in interviews, testing your knowledge of both standard practices and the underlying Spring mechanics.

1. The "What": Enabling Validation and Annotating DTOs

At its core, request validation is about enforcing rules on incoming data before it reaches your business logic. Spring Boot provides first-class support for this through the Jakarta Bean Validation API (formerly JSR 380) and its reference implementation, Hibernate Validator.

You don't need to write if/else blocks to check for nulls, empty strings, or size constraints. Instead, you declare these constraints directly on your Data Transfer Objects (DTOs) using annotations.

Let's begin by seeing how to set this up and what the most common annotations look like in a real-world example.

Spring Boot and Validation: A Complete Guide with @ ...

The following article, 'Spring Boot and Validation: A Complete Guide', provides a clear, practical introduction. It covers the necessary dependency, a handy list of common annotations, and a concrete DTO example that we will refer to throughout this lesson.

Please read sections '2. Enabling Validation', '3. Basic Validation Annotations', and '4. Example: ProductRequest DTO'. This will show you the necessary dependency, a list of common constraints, and how they are applied in a practical example.

As you saw, adding the spring-boot-starter-validation dependency is all it takes for Spring Boot to auto-configure the validation infrastructure. The power of this approach lies in its declarative nature: the ProductRequest DTO is now a self-documenting contract for what constitutes a valid product.


2. The "How": Triggering Validation in Controllers

Now that we have an annotated DTO, how do we tell Spring to enforce these rules for an incoming API request? This is done with a single annotation in your controller: @Valid.

When you annotate a @RequestBody with @Valid, you instruct Spring to run the validation process on the deserialized object before executing your controller method.

@RestController
@RequestMapping("/products")
public class ProductController {

    @PostMapping
    public ResponseEntity<String> createProduct(@Valid @RequestBody ProductRequest request) {
        // If the code reaches this point, the 'request' object is guaranteed to be valid.
        // ... business logic to create the product
        return ResponseEntity.ok("Product created successfully!");
    }
}

What Happens Internally?

This is a key detail for an interview. When Spring's DispatcherServlet processes this request, the RequestResponseBodyMethodProcessor is responsible for handling the @RequestBody.

  1. It first deserializes the incoming JSON into a ProductRequest object.
  2. It sees the @Valid annotation.
  3. It invokes the configured Validator (Hibernate Validator by default).
  4. The validator checks the ProductRequest object against the constraints defined by its annotations (@NotBlank, @NotNull, @Positive, etc.).
  5. If any constraint is violated, the validator throws a MethodArgumentNotValidException. This exception contains detailed information about every single field that failed validation.
  6. This exception propagates up, and if not handled, results in a default Spring Boot error response.

A crucial part of a robust API is to gracefully handle that MethodArgumentNotValidException and return a clean, structured 400 Bad Request response to the client.

The standard way to do this is with a global exception handler using @RestControllerAdvice. Let's take a quick look at how this is implemented.

Spring Boot and Validation: A Complete Guide with @ ...

When validation fails, Spring throws an exception. A robust API must catch this and return a clean, structured error response. The same article shows the standard pattern for this using a global exception handler.

Read section '9. Global Exception Handling for Validation'. Pay close attention to the handler for MethodArgumentNotValidException and the structure of the JSON response it creates. This is a preview of our next lesson, but it's important to see the complete flow now.

This handler transforms the exception into a user-friendly JSON payload, which is essential for good API design. We will explore @ControllerAdvice in much greater detail in our next lesson.


3. Advanced Validation: Contexts and Custom Logic

The basics cover many use cases, but senior-level interviews often probe your knowledge of more complex scenarios. Let's cover three important advanced topics.

@Valid vs. @Validated: A Classic Interview Question

You may have also seen the @Validated annotation. While it looks similar to @Valid, it serves a different purpose.

Annotation Origin Use Case Supports Groups
@Valid Jakarta Bean Validation Standard annotation to trigger validation on nested objects and method parameters (like @RequestBody). No (at method level)
@Validated Spring Framework A Spring-specific variant. Its key feature is support for validation groups. Also used for method-level validation on service beans. Yes

You will almost always use @Valid for request bodies. You reach for @Validated when you need to apply different sets of validation rules based on the context.

Validation Groups for Different Contexts

Imagine the ProductRequest DTO. When creating a product, price and categoryId should not be null. However, when updating a product, a user might only send the name to change it, leaving the price and category unchanged. We need different rules for CREATE vs. UPDATE. This is what validation groups are for.

The process is simple:

  1. Define marker interfaces for your groups.
    public interface OnCreate {}
    public interface OnUpdate {}
    
  2. Assign groups to your annotations in the DTO.
    public class ProductRequest {
        @NotBlank(groups = {OnCreate.class, OnUpdate.class})
        String name;
    
        @NotNull(groups = OnCreate.class) // Only required on create
        @Positive(groups = {OnCreate.class, OnUpdate.class})
        Double price;
    }
    
  3. Use @Validated in your controller to specify which group to apply.
    @PostMapping
    public ResponseEntity<ProductResponse> createProduct(
        @Validated(OnCreate.class) @RequestBody ProductRequest request
    ) { ... }
    
    @PutMapping("/{id}")
    public ResponseEntity<ProductResponse> updateProduct(
        @PathVariable Long id,
        @Validated(OnUpdate.class) @RequestBody ProductRequest request
    ) { ... }
    

Now, when the createProduct endpoint is called, the @NotNull constraint on price will be enforced. When updateProduct is called, it will be ignored.

Custom Validators

What if you have a business rule that isn't covered by standard annotations? For example, a SKU that must follow a specific format like PROD-12345. You can easily create your own custom validation annotation.

This involves two parts:

  1. The Annotation Interface: You define an annotation and link it to its validator implementation using @Constraint.
  2. The Validator Implementation: You create a class that implements ConstraintValidator<YourAnnotation, TypeToValidate> and put your validation logic in the isValid() method.

Let's see a practical example for creating a custom validator.

How Spring Boot Handles Validation Annotations

Creating custom validators is a powerful feature that lets you encapsulate complex, reusable business rules. This reading from the 'How Spring Boot Handles Validation Annotations' article shows the complete process.

Read the section 'Custom Validation with ConstraintValidator'. Focus on the two main components: the custom annotation interface (e.g., @EvenNumber) and the validator class that implements ConstraintValidator (e.g., EvenNumberValidator).

Test your understanding!

You need to validate a field that must be a valid ISO 3166-1 alpha-2 country code (e.g., "US", "DE", "IN"). Standard annotations like @Size(min=2, max=2) are not sufficient because they would allow "XX". Briefly outline the two main components you would create to implement a custom @CountryCode validator.

Show answer
  1. Annotation (@CountryCode): I would create a custom annotation interface named @CountryCode. It would be annotated with @Constraint(validatedBy = CountryCodeValidator.class), @Target({ElementType.FIELD}), and @Retention(RetentionPolicy.RUNTIME). It would also include message(), groups(), and payload() elements as required by the Bean Validation spec.

  2. Validator (CountryCodeValidator): I would create a class named CountryCodeValidator that implements ConstraintValidator<CountryCode, String>. Inside its isValid(String value, ...) method, I would check if the input value exists in the set of valid ISO country codes (e.g., by checking against Locale.getISOCountries()). The method would return true if it's a valid code and false otherwise.


Conclusion

You have now learned how to implement a robust validation layer in a Spring Boot application, a fundamental skill for any backend developer. Being able to discuss these concepts with depth—from the basic annotations to the internal exception flow and advanced custom logic—will set you apart in a technical interview.

Key Takeaways:

  • Declarative Validation: Use spring-boot-starter-validation to apply constraint annotations directly to your DTOs.
  • Triggering Validation: Use @Valid on @RequestBody in your controllers to trigger the validation process.
  • Error Handling: Validation failures throw a MethodArgumentNotValidException, which should be handled by a @RestControllerAdvice to provide clean 400 Bad Request responses.
  • Contextual Rules: Use @Validated with validation groups (e.g., OnCreate, OnUpdate) to apply different constraints in different scenarios.
  • Custom Logic: When standard annotations aren't enough, create your own by defining a custom annotation and implementing the ConstraintValidator interface.

Preview of the Next Lesson:

In this lesson, we had a preview of how to handle validation exceptions. In our next session, we will do a deep dive into this mechanism. You will implement centralized exception handling using @ControllerAdvice. This will allow you to manage not just validation errors, but all types of exceptions (business logic, security, etc.) in one place to create truly professional and resilient REST APIs.