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.
Annotation | Usage |
---|---|
@AssertFalse | The annotated element must be false. |
@AssertTrue | The annotated element must be true. |
@DecimalMax | The annotated element must be a number whose value must be lower or equal to the specified maximum. |
@DecimalMin | The annotated element must be a number whose value must be higher or equal to the specified minimum. |
@Future | The annotated element must be an instant, date or time in the future. |
@Max | The annotated element must be a number whose value must be lower or equal to the specified maximum. |
@Min | The annotated element must be a number whose value must be higher or equal to the specified minimum. |
@Negative | The annotated element must be a strictly negative number. |
@NotBlank | The annotated element must not be null and must contain at least one non-whitespace character. |
@NotEmpty | The annotated element must not be null nor empty. |
@NotNull | The annotated element must not be null . |
@Null | The annotated element must be null . |
@Pattern | The annotated CharSequence must match the specified regular expression. |
@Positive | The annotated element must be a strictly positive number. |
@Size | The 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 !!
Comments