REST API Request Validation with Spring Boot

In this spring boot exception handler tutorial, we will learn to validate request body sent to PUT/POST REST APIs. We will also learn to add custom error messages in API responses for validation errors.

Table of Contents

1. Create REST APIs and model classes
2. Request validation and exception handling
3. Demo of request validations
4. Available annotations to use
5. Summary

In this spring boot example, we will see primarily two major validation cases –

  1. HTTP POST /employees and request body does not contain valid values or some fields are missing. It will return HTTP status code 400 with proper message in response body.
  2. HTTP GET /employees/{id} and INVALID ID is sent in request. It will return HTTP status code 404 with proper message in response body.

Read More : HTTP status codes

1. Create REST APIs and model classes

Given REST APIs are from employee management module.

@PostMapping(value = "/employees")
public ResponseEntity<EmployeeVO> addEmployee (@RequestBody EmployeeVO employee)
{
    EmployeeDB.addEmployee(employee);
    return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}

@GetMapping(value = "/employees/{id}") 
public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id)
{
    EmployeeVO employee = EmployeeDB.getEmployeeById(id);
    
    if(employee == null) {
    	 throw new RecordNotFoundException("Invalid employee id : " + id);
    }
    return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}
@XmlRootElement(name = "employee")
@XmlAccessorType(XmlAccessType.FIELD)
public class EmployeeVO extends ResourceSupport implements Serializable
{
	private Integer employeeId;
	private String firstName;
	private String lastName;
	private String email;

	public EmployeeVO(Integer id, String firstName, String lastName, String email) {
		super();
		this.employeeId = id;
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = email;
	}

	public EmployeeVO() {
	}
	
	//Removed setter/getter for readability
}

2. Spring boot exception handling – REST request validation

2.1. Default spring validation support

To apply default validation, we only need to add relevant annotations in proper places. i.e.

  1. Annotate model class with required validation specific annotations such as @NotEmpty, @Email etc.
    @XmlRootElement(name = "employee")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EmployeeVO extends ResourceSupport implements Serializable
    {
    	private static final long serialVersionUID = 1L;
    	
    	public EmployeeVO(Integer id, String firstName, String lastName, String email) {
    		super();
    		this.employeeId = id;
    		this.firstName = firstName;
    		this.lastName = lastName;
    		this.email = email;
    	}
    
    	public EmployeeVO() {
    	}
    
    	private Integer employeeId;
    
    	@NotEmpty(message = "first name must not be empty")
    	private String firstName;
    
    	@NotEmpty(message = "last name must not be empty")
    	private String lastName;
    
    	@NotEmpty(message = "email must not be empty")
    	@Email(message = "email should be a valid email")
    	private String email;
    	
    	//Removed setter/getter for readability
    }
    
  2. Enable validation of request body by @Valid annotation
    @PostMapping(value = "/employees")
    public ResponseEntity<EmployeeVO> addEmployee (@Valid @RequestBody EmployeeVO employee)
    {
        EmployeeDB.addEmployee(employee);
        return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
    }
    

2.2. Exception model classes

Default spring validation works and provide information overload about error, and that’s why we should customize it according to our application’s need. We shall provide only required error information with very clear wordings. Extra information is also not suggested.

It is always a good advise to create exceptions that are meaningful and describe the problem well enough. One way is to create seperate classes to denote specific business usecase failure and return them when that usecase fail.

Read more: Java Exception Handling – A New Appoarch

e.g. I have created RecordNotFoundException class for all buch scenarios where a resource is requested by it’s ID, and resource is not found in the system.

package com.howtodoinjava.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException 
{
	public RecordNotFoundException(String exception) {
		super(exception);
	}
}

Similarly, I have wrote an special class which will be returned for all failure cases. Having consistent error message structure for all APIs, help the API consumers to write more robust code.

import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "error")
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;

	//Getter and setters
}

2.3. Custom ExceptionHandler

Now add one class extending ResponseEntityExceptionHandler and annotate it with @ControllerAdvice annotation.

ResponseEntityExceptionHandler is a convenient base class for to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. @ControllerAdvice is more for enabling auto-scanning and configuration at application startup.

Java program for @ControllerAdvice exception handling example.

package com.howtodoinjava.demo.exception;

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

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
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;

@SuppressWarnings({"unchecked","rawtypes"})
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler 
{
	@ExceptionHandler(Exception.class)
	public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Server Error", details);
		return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler(RecordNotFoundException.class)
	public final ResponseEntity<Object> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Record Not Found", details);
		return new ResponseEntity(error, HttpStatus.NOT_FOUND);
	}

	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus 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);
	}
}

Above class handles multiple exceptions including RecordNotFoundException; and it also handle request validation errors in @RequestBody annotated object. Let’s see how it works.

3. Spring boot exception handling – Demo

1) HTTP GET /employees/1 [VALID]

HTTP Status : 200

{
    "employeeId": 1,
    "firstName": "John",
    "lastName": "Wick",
    "email": "howtodoinjava@gmail.com",
}

2) HTTP GET /employees/23 [INVALID]

HTTP Status : 404

{
    "message": "Record Not Found",
    "details": [
        "Invalid employee id : 23"
    ]
}

3) HTTP POST /employees [INVALID]

{
    "lastName": "Bill",
    "email": "ibill@gmail.com"
}
HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "first name must not be empty"
    ]
}

4) HTTP POST /employees [INVALID]

{
    "email": "ibill@gmail.com"
}
HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "last name must not be empty",
        "first name must not be empty"
    ]
}

5) HTTP POST /employees [INVALID]

{
	"firstName":"Lokesh",
    "email": "ibill_gmail.com" //invalid email in request
}
HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "last name must not be empty",
        "email should be a valid email"
    ]
}

4. REST request validation annotations

In above example, we used only 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).

5. Summary

In this spring REST validation tutorial, we learned to –

  • validate ID when fetching resource by ID.
  • validate request body fields in POST/PUT APIs.
  • send consistent and structured error response in API responses.

Drop me your questions related to spring rest exception handling.

Happy Learning !!

References : Package javax.validation.constraints

5 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments

Comments are closed for this article!

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.