Skip to main content

Welcome to a Grasp lesson

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

Lesson 22

Bean Validation with Jakarta Annotations

Recommended resources

Introduction

Hello! In our last lesson, we learned how to accept and return JSON data using @RequestBody and @RestController. This allows us to handle the "happy path" where a client sends a perfectly formed request. However, in any real-world system, we cannot trust incoming data. It might be incomplete, malformed, or outside acceptable boundaries.

Today's lesson directly addresses this by focusing on our next learning outcome: Implement input validation using Jakarta Bean Validation annotations (@Valid, @NotNull, @Size).

We will explore Spring Boot's powerful and declarative approach to server-side validation. You'll learn how to define validation rules directly on your data transfer objects (DTOs) using a standard set of annotations and then trigger this validation in your controller. We'll also build a centralized exception handler to transform default validation errors into clean, structured, and client-friendly API responses.


The Validation Stack: Jakarta Bean Validation and Hibernate Validator

Before diving into the code, let's look at the components involved. Spring Boot doesn't reinvent the wheel for validation. Instead, it integrates a standard Java specification called Jakarta Bean Validation. This specification provides a set of annotations and an API for defining and validating object constraints.

The most popular implementation of this specification is Hibernate Validator, and this is the one Spring Boot auto-configures for you when you include the necessary dependency.

Spring Boot Validation Architecture
This diagram shows the layered architecture for validation. Your Spring Boot application uses the `spring-boot-starter-validation`, which brings in Hibernate Validator. Hibernate Validator, in turn, is an implementation of the standard Jakarta Bean Validation API.

This layered design is a great example of dependency inversion. Your code will primarily depend on the standard Jakarta API annotations (like @NotNull), making it portable and decoupled from the specific implementation (Hibernate Validator).


Why Server-Side Validation is Crucial

You might wonder why we need server-side validation if the frontend can validate user input. The simple answer is that you can never trust the client. A malicious user or a faulty client can easily bypass frontend validation and send direct HTTP requests to your API. Without server-side validation, this could lead to corrupting your database with invalid data, causing application errors, or creating security vulnerabilities.

Let's watch a short segment that demonstrates the consequences of not having validation.

Spring Boot | REST API Request Validation & Exception Handling Realtime Example | JavaTechie

The video 'REST API Request Validation & Exception Handling' by Java Techie clearly shows what happens when an API accepts invalid data.

Watch from 14:53 to 18:22. Notice how Postman is used to send a request with an empty username, an invalid email, and other incorrect data. The application accepts it without complaint and saves the corrupted data to the database. This is the exact problem we are about to solve.


Enabling and Applying Validation

To get started, we need two things: the right dependency and a way to trigger validation.

1. Adding the Validation Starter

First, ensure your project includes the spring-boot-starter-validation dependency. In most cases, if you created your project with spring-boot-starter-web, this dependency is already included as a transitive dependency.

Validation with Spring Boot - the Complete Guide

The article 'Validation with Spring Boot' by Reflectoring provides a good summary of the setup process.

Read the section 'Using the Spring Boot Validation Starter'. It confirms the dependency name and its relationship with the web starter.

2. Using @Valid to Trigger Validation

To activate validation for an incoming request body, you apply the @Valid annotation to the @RequestBody parameter in your controller method.

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest userRequest) {
    // ... logic to create user
}

When Spring sees @Valid, it automatically runs the object through the configured validator (Hibernate Validator) before executing your method body. If any validation rules are violated, it throws a MethodArgumentNotValidException.


Defining Constraints with Annotations

The core of the process is annotating the fields of your DTO (the class mapped to the request body) with constraint annotations from the jakarta.validation.constraints package.

Let's explore the most common annotations. We'll use a comprehensive video that walks through applying them to a User DTO.

Spring Boot Validations Explained | @Valid, Annotations & Custom Validation

The video 'Spring Boot Validations Explained' from The Curious Coder provides an excellent tour of the most common validation annotations. We'll watch it in segments.

First, watch from 03:10 to 04:59. This segment demonstrates adding the spring-boot-starter-validation dependency and shows the crucial role of the @Valid annotation in the controller.

Now, let's look at the specific constraint annotations you'll be using on your DTO fields.

Presence and Content Annotations (@NotNull, @NotEmpty, @NotBlank)

These three annotations are often confused, but they have distinct purposes for validating strings and collections.

  • @NotNull: The value cannot be null. An empty string "" is valid.
  • @NotEmpty: The value cannot be null and its size/length must be greater than 0. This applies to strings, collections, maps, and arrays. A string with only whitespace " " is valid.
  • @NotBlank: The value cannot be null and must contain at least one non-whitespace character. This is for strings only.

Spring Boot Validations Explained | @Valid, Annotations & Custom Validation

Let's continue with the same video to see these three annotations in action.

Watch from 04:59 to 15:36. The video clearly demonstrates the difference between @NotNull, @NotEmpty, and @NotBlank using practical examples.

Size, Range, and Format Annotations (@Size, @Min, @Max, @Email, @Pattern)

This next group of annotations allows you to enforce constraints on length, numeric ranges, and string formats.

  • @Size(min=, max=): Constrains the size of a string, collection, map, or array.
  • @Min(value) / @Max(value): Constrains a number to be above or below a certain value.
  • @Email: Constrains a string to have a valid email format.
  • @Pattern(regexp=): Constrains a string to match a given regular expression, offering maximum flexibility.

Spring Boot Validations Explained | @Valid, Annotations & Custom Validation

The next segment of the video covers these size, numeric, and format validators, along with a few others.

Watch the following segments: @Size and Numeric Annotations (15:49 - 23:09): Covers @Size, @Min, @Max, @Positive, and @Digits. Email and Regex Annotations (23:09 - 30:19): Covers @Email for basic format and @Pattern for advanced regex validation. Date Annotations (30:19 - 32:01): Briefly shows @Past and @Future for date validation. Collection Validation (32:01 - 34:13): Demonstrates applying @Size to a List.


Under the Hood: How Annotations Work

Given your background, you're likely curious about what's happening behind these annotations. Each validation annotation is linked to a ConstraintValidator implementation. This is a simple interface with two key methods:

  1. initialize(Annotation an): Called once to configure the validator with values from the annotation (e.g., the min and max values from @Size).
  2. isValid(Object value, ConstraintValidatorContext context): This is where the actual validation logic resides. It returns true if the value is valid and false otherwise.

This is a clean application of the Strategy design pattern, where each annotation's validation strategy is encapsulated in its own validator class.

How Spring Boot Handles Validation Annotations - Medium

The article 'How Spring Boot Handles Validation Annotations' provides a clear look at the internal mechanics.

Read the section 'Mechanics of Built-In Validation Annotations'. Focus on the code examples for NotNullValidator, SizeValidatorForCharSequence, and EmailValidator. This will show you the simple, concrete logic that executes when you use these annotations.


Handling Validation Errors

As mentioned, a validation failure throws a MethodArgumentNotValidException. By default, Spring Boot returns a 400 Bad Request response with a verbose, hard-to-parse JSON body. To provide a clean API, we need to catch this exception and format the response ourselves.

The standard Spring pattern for this is to create a global exception handler using the @RestControllerAdvice annotation.

Spring Boot Validations Explained | @Valid, Annotations & Custom Validation

Both videos we've used demonstrate this pattern. Let's return to 'The Curious Coder' as it builds the solution step-by-step.

Watch from 06:07 to 12:31. This is a crucial section. It shows how to: Create a class annotated with @RestControllerAdvice. Create a method annotated with @ExceptionHandler(MethodArgumentNotValidException.class) to specifically catch validation exceptions. Iterate through the field errors to extract the field name and the custom error message you defined on the annotation. Build a Map of fieldName -> errorMessage to return as a clean JSON response. Use @ResponseStatus(HttpStatus.BAD_REQUEST) to ensure the correct HTTP status code is returned.

This approach is highly effective because it centralizes all validation error handling in one place, keeping your controllers clean and focused on their primary logic.


Conclusion

Today you've added a critical layer of robustness to your API. By adopting declarative validation, you've made your code cleaner, more readable, and more secure without writing tedious if/else boilerplate.

Key Takeaways:

  • Server-side validation is mandatory to protect your application and data integrity.
  • Spring Boot uses Hibernate Validator, an implementation of the Jakarta Bean Validation standard.
  • Validation is enabled by including the spring-boot-starter-validation dependency.
  • You apply constraint annotations (e.g., @NotNull, @Size, @Email) to the fields of your DTOs. Each annotation can have a custom message.
  • Validation is triggered by placing the @Valid annotation on the @RequestBody in your controller.
  • A validation failure throws a MethodArgumentNotValidException.
  • You should implement a global exception handler using @RestControllerAdvice and @ExceptionHandler to catch this exception and return a structured, user-friendly error response.

In our next lesson, we will expand on what we did in the exception handler. We'll take a deeper dive into controlling HTTP responses by learning how to return appropriate HTTP status codes and headers using ResponseEntity. This will give you full command over every part of the HTTP response for both successful operations and error conditions.