Validating data in request’s payload must be handled by any REST APIs, in order to make sure that clients won’t send misinformation to the server, which can break data integrity of the application.

In this Spring Boot REST API tutorial, you will learn how to validate REST API request with Spring framework, Java Bean Validation and Hibernate Validator. In case of invalid request, the API will return HTTP status code 400 (Bad Request) with developer-friendly error message in the response body. You’ll also learn how to customize error message in the response.

 

NOTES: this article is about validating request body of REST API requests. For validation of path variables and query parameters, kindly check the following articles:

 

1. About Java Bean Validation and Hibernate Validator

Java Bean Validation (now Jakarta Bean Validation) is a Java specification which lets you express constrains on object models via annotations such as @NotNull, @Null, @Min, @Max... It provides APIs to validate objects and object graphs, and you can also write custom constraints if the built-in constraints do not meet your need.

Hibernate Validator is a reference implementation of Java Bean Validation specification, plus it provides additional built-in constrains and APIs which gives you more options for validating objects.

In a Spring Boot application, Hibernate framework will be used by default if Spring Data JPA is used - but without Hibernate Validator.


2. Add Required Dependency for Validation

In order to use Java Bean validation with Hibernate Validator, you need to add the following dependency to your Maven project:

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


This will add jakarta.validation-api-VERSION.jar and hibernate-validator-VERSION.jar files to the project’s classpath.


3. Sample REST API Code

Suppose that we’re developing some REST APIs for products management with end point path is /api/v1/products. Below is code of the entity class that represents product information:

package net.codejava;

import javax.persistence.*;

@Entity
@Table(name = "products")
public class Product {
	
	@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	@Column(nullable = false, length = 512, unique = true)
	private String name;
	
	private float price;
	
	public Product() { }
	
	public Product(String name, float price) {
		this.name = name;
		this.price = price;
	}

	// getters and setters are not shown for brevity		
}
You can see the @Column annotation is used with nullable = false, length = 512 and unique = true - they are not validation constraints. Instead, they are used for generating database schema. You can use any database (I use H2 in-memory database for demo purpose).

We use Spring Data JPA, so declare the corresponding repository interface as follows:

package net.codejava;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Integer> {

}
And the following code is of a @RestController class that implements an API for adding new product information (the addOne() method):

package net.codejava;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/products")
public class ProductApi {

	private ProductRepository repo;

	public ProductApi(ProductRepository repo) {
		this.repo = repo;
	}
	

	@PostMapping
	public Product addOne(@RequestBody Product product) {
		return repo.save(product);
	}
}
When adding new product, it requires that the product name is not blank. Run the following curl command that tries to call the API to add a product without name (only price is specified):

curl -v -H "Content-Type: application/json" -d "{\"price\": 100}" localhost:8080/api/v1/products
Since a constraint in the database prevents null value of a product, surely we will get status code 500 Internal Server Error with lengthy exception information included in the response body, as shown below:

test add product internal server error

Imagine if no such constraint nullable = false in database, a product with null name will be stored, breaking data integrity of the application. And also, the error message with length exception is not clear and friendly to developers.

In terms of REST API best practice, the API should return status code 400 Bad Request if the data in the request’s payload does not meet the required constraints. And it should include clear error message that helps developers to rectify the issue.

If you want to recap the basics of REST APIs with Spring Boot, check this tutorial.


4. Add Bean Validation Annotations

Let’s update the Product entity class by adding a validation constraint that requires product name must not have blank value. Annotate the field name with the @NotBlank annotation:

public class Product {	
	
	@Column(nullable = false, length = 512, unique = true)
	@NotBlank(message = "Product name cannot be blank")
	private String name;
	
	...
}
Import the @NotBlank annotation from the javax.validation.constraints package. And we also need to use the @Valid annotation in the handler method’s signature as follows:

import javax.validation.Valid;
...

@RestController
@RequestMapping("/api/v1/products")
public class ProductApi {

	...
	
	@PostMapping
	public Product addOne(@RequestBody @Valid Product product) {
		return repo.save(product);
	}
}
Now, run this curl command again:

curl -v -H "Content-Type: application/json" -d "{\"price\": 100}" localhost:8080/api/v1/products
With bean validation annotations applied, now we get status code 400 Bad Request and the exact error message is hard to be found in the midst of lengthy exception:

test valid product name

The response status code is now correct (400). Next, we want to customize the error message to make it clear and succinct - easier for developers to spot the cause of the error.

I recommend you take this course to get extensive REST API development experience with Spring framework.


5. Customize Validation Error Message

In the console you can see this WARN logging message:

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0]...
So in order to customize the validation error message, we need to use Spring AOP to intercept the API handler method each time a MethodArgumentNotValidException is thrown.

Create a custom exception handler class with the following code:

package net.codejava;

import java.util.*;
import java.util.stream.Collectors;

import org.springframework.http.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
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, HttpStatus status, WebRequest request) {
			
		Map<String, Object> responseBody = new LinkedHashMap<>();
		responseBody.put("timestamp", new Date());
		responseBody.put("status", status.value());
		
		List<String> errors = ex.getBindingResult().getFieldErrors()
			.stream()
			.map(x -> x.getDefaultMessage())
			.collect(Collectors.toList());
		
		responseBody.put("errors", errors);
		
		return new ResponseEntity<>(responseBody, headers, status);
	}

}
Here, the method handleMethodArgumentNotValid() will be invoked if any handler method throws MethodArgumentNotValidException (a kind of global exception handler). It returns a JSON document that represents an error message with 3 field: timestamp, status and a list of validation error messages taken from the validation annotations in the entity class.

Now run the following curl command (append the | jq option to prettify JSON):

curl -H "Content-Type: application/json" -d "{\"price\": 100}" localhost:8080/api/v1/products | jq
Then you can see the JSON in the response is very clear with exact validation error message:

test valid product customized error message

Let’s add more validation annotations to the Product entity class, as follows:

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

import org.hibernate.validator.constraints.Length;

@Entity
@Table(name = "products")
public class Product {
	
	...
	
	@Column(nullable = false, length = 512, unique = true)
	@NotBlank(message = "Product name cannot be blank")
	@Length(min = 5, max = 512, message = "Product name must be between 5-512 characters")
	private String name;
	
	@Min(value = 10, message = "Product price must be greater or equal to 10") 
	@Max(value = 9999, message = "Product prices must be less than or equal to 9999")
	private float price;
	
	...
	
}
As you can see, @Length is a Hibernate Validator’s annotation - it is used to ensure the length of product name is between the range of 5 to 512 characters. And @Min and @Max are Java Bean Validation’s annotations. They are used to ensure that the product price is between 10 and 9999.

To test, run the following curl command that tries to add a product with very short name and very low price:

curl -H "Content-Type: application/json" -d "{\"name\": \"Sony\", \"price\": 6}" localhost:8080/api/v1/products | jq
This will give the following response:

test validation error messages

You see, it reports the validation error with exact messages, which help the developers fix the error quickly.

 

6. List of Jakarta Bean Validation’s Constraints

For your reference, below is the list of built-in constraints defined by Jakarta Bean Validation specification, which can be applied for most types in JDK:

  • AssertFalse: The annotated element must be false (use for boolean and Boolean types).
  • AssertTrue: The annotated element must be true. Supported types same as AssertFalse.
  • DecimalMax: The annotated element must be a number whose value must be lower or equal to the specified maximum. Supported types are: BigDecimal, BigInteger, CharSequence, byte, short, int, long, and their respective wrappers.
  • DecimalMin: The annotated element must be a number whose value must be higher or equal to the specified minimum. Supported types same as DecimalMax.
  • Digits: The annotated element must be a number within accepted range. Supported types same as DecimalMax.
  • Email: The annotated element must be a well-formed email address. Supported types are CharSequence.
  • Future: The annotated element must be a date in the future. Supported types are: java.util.Date and java.util.Calendar.
  • Max: The annotated element must be a number whose value must be lower or equal to the specified maximum. Supported types same as DecimalMax.
  • Min: The annotated element must be a number whose value must be higher or equal to the specified minimum. Supported types same as DecimalMax.
  • NotBlank: The annotated element must not be null and must contain at least one non-whitespace character. Supported types are CharSequence.
  • NotEmpty: The annotated element must not be null nor empty. Supported types are: CharSequence, Collection, Map, Array.
  • NotNull: The annotated element must not be null. Accepts any type.
  • Null: The annotated element must be null. Accepts any type.
  • Past: The annotated element must be a date in the past. Supported types same as Future.
  • Pattern: The annotated CharSequence must match the specified regular expression.
  • Size: The annotated element size must be between the specified boundaries (included). Supported types are: CharSequence, Collection, Map, Array.
Click here to see the full list of Jakarta Bean Validation’s constraints.


7. List of Hibernate Validator’s Constraints

And below are some built-in constraints defined by Hibernate Validator, for your reference:

  • CreditCardNumber: The annotated element has to represent a valid credit card number.
  • ISBN: Checks that the annotated character sequence is a valid ISBN.
  • Length: Validates that the string is between min and max included.
  • Range: The annotated element has to be in the appropriate range. Apply on numeric values or string representation of the numeric value.
  • URL: Validates the annotated string is an URL.
Click here to see the full list of Hibernate Validator’s constraints.

That’s my tutorial about validating REST API requests with Spring Boot, Java Bean Validation and Hibernate Validator. I hope you found it helpful. You can get the sample project code from this GitHub repo.

To see the coding in action, watch the following video:

 

References:

 

Related REST API Tutorials:


About the Author:

is certified Java programmer (SCJP and SCWCD). He started programming with Java in the time of Java 1.4 and has been falling in love with Java since then. Make friend with him on Facebook and watch his Java videos you YouTube.



Add comment

   


Comments 

#2vijay2023-06-29 07:23
Well documented ..for a quick read its worth the time . THanks
Quote
#1Sachin2023-03-01 04:01
i get below error Error 500: javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/persistence/Persistence
Quote