Spring Boot REST Validation Example

In this Spring Boot REST API validation tutorial, we will learn to validate the request body sent to PUT/POST REST APIs. We will look at how to validate domain objects in Spring Boot and also learn to add custom error messages in API responses for validation errors.

In this spring boot example, we will primarily use the HTTP POST API where the request body does not contain valid values or some fields are missing. It will return HTTP status code 400 with a proper message in the response body.

See Also : HTTP Status Codes

1. Maven

Starting with Boot 2.3, we need to explicitly add the spring-boot-starter-validation dependency for the validation feature along with spring-boot-starter-web for writing the REST APIs.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</artifactId> 
</dependency>

2. Domain Model with Bean Validation Annotations

For the demo, we are creating a simple Employee class as the domain. It has the necessary fields for storing the employee’s information and Jakarta Bean Validation annotations.

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

  Long id;

  @NotBlank(message = "First name is required")
  @Size(min = 3, max = 20, message = "First name should be between 3 and 20 characters")
  String firstName;

  @NotBlank(message = "Last name is required")
  @Size(min = 3, max = 20, message = "Last name should be between 3 and 20 characters")
  String lastName;

  @NotBlank(message = "Email is required")
  @Email(message = "Email is invalid")
  String email;
}

3. Add @Valid in the REST Controller

After the domain class has been annotated, we will move to the REST controller. The following controller class demonstrates two simple API calls (GET and POST).

In POST API, we validate the input request containing the employee data. Notice that we annotate the Employee parameter with @RequestBody and @Valid annotations. The @Valid annotation causes the validation triggered for:

  • Class property
  • Method parameter
  • Method return type
@RestController
@RequestMapping("/employees")
public class EmployeeController {

  @PostMapping
  public ResponseEntity<?> showRegistrationForm(@Valid @RequestBody Employee employee) {
  
    //TODO: Save the employee
    return ResponseEntity.ok(employee);
  }
}

4. Handle MethodArgumentNotValidException in @ExceptionHandler

When Spring Boot finds a method argument annotated with @Valid, it automatically bootstraps the default JSR 380 implementation (Hibernate Validator) and validates the argument. When the target method argument fails to pass the validation, Spring Boot throws a MethodArgumentNotValidException exception.

We can handle the MethodArgumentNotValidException either locally inside the controller class or globally configured using a @ControllerAdvice annotated method.

4.1. Handle Exceptions ‘Locally’ in @Controller Class

This type of handling shall be used when we are handling in a way very specific to that controller only, and that logic is not needed in other places.

@RestController
@RequestMapping("/employees")
public class EmployeeController {

  //...

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex) {
    
    List<String> details = new ArrayList<>();
    for (ObjectError error : ex.getBindingResult().getAllErrors()) {
      details.add(error.getDefaultMessage());
    }
    ErrorResponse error = new ErrorResponse("Validation Failed", details);
    return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
  }
}

4.2. Handle Exceptions ‘Globally’ in @ControllerAdvice Class

When we duplicate the exception-handling logic in multiple paces, we can write in a single class so that all the REST controllers can use it.

This is a good place to handle other specific exceptions such as RecordNotFoundException. Whenever the RecordNotFoundException is thrown from a controller method, the handleUserNotFoundException() method will be invoked and the repose will be sent back to the client.

In a similar fashion, we can define other exception handler methods also.

import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {


  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, 
           HttpHeaders headers, HttpStatusCode status, WebRequest request) {

    List<String> details = new ArrayList<>();
    for (ObjectError error : ex.getBindingResult().getAllErrors()) {
      details.add(error.getDefaultMessage());
    }
    ErrorResponse error = new ErrorResponse("Validation Failed", details);
    return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
  }
}

For reference, we have used the following custom classes for representing the validation errors in the application.

@Data
@NoArgsConstructor
public class ErrorResponse {

  public ErrorResponse(String message, List<String> details) {
    super();
    this.message = message;
    this.details = details;
  }

  //General error message about nature of error
  private String message;

  //Specific errors in API request processing
  private List<String> details;
}

5. Demo

Now start the application and let’s test the API for various inputs and check the validation error messages.

Start with sending an empty request body and it should ask for all the required fields.

HTTP POST http://localhost:8080/employees

Request:

{ }

Response:

{
    "message": "Validation Failed",
    "details": [
        "Last name is required",
        "First name is required",
        "Email is required"
    ]
}

Let’s add a few fields but with insufficient size.

HTTP POST http://localhost:8080/employees

Request:

{
    "firstName": "Lokesh",
    "lastName": "G"
}

Response:

{
    "message": "Validation Failed",
    "details": [
        "Last name should be between 3 and 20 characters",
        "Email is required"
    ]
}

6. Bean Validation Annotations

In the above example, we used only a few annotations such as @NotEmpty and @Email. There are more such annotations to validate request data. Check them out when needed.

AnnotationUsage
@AssertFalseThe annotated element must be false.
@AssertTrueThe annotated element must be true.
@DecimalMaxThe annotated element must be a number whose value must be lower or equal to the specified maximum.
@DecimalMinThe annotated element must be a number whose value must be higher or equal to the specified minimum.
@FutureThe annotated element must be an instant, date or time in the future.
@MaxThe annotated element must be a number whose value must be lower or equal to the specified maximum.
@MinThe annotated element must be a number whose value must be higher or equal to the specified minimum.
@NegativeThe annotated element must be a strictly negative number.
@NotBlankThe annotated element must not be null and must contain at least one non-whitespace character.
@NotEmptyThe annotated element must not be null nor empty.
@NotNullThe annotated element must not be null.
@NullThe annotated element must be null.
@PatternThe annotated CharSequence must match the specified regular expression.
@PositiveThe annotated element must be a strictly positive number.
@SizeThe annotated element size must be between the specified boundaries (included).

7. Summary

In this Spring Boot REST validation example, we learned to –

  • validate request body fields in POST/PUT APIs.
  • send consistent and structured error responses in API responses.

Drop me your questions if you have any.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
5 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode