Validating requests should be mandatory in any REST API oriented applications. API request validation includes checking request body, checking path variables and checking query parameters in order to ensure that the requests containing valid data as required by API operations.

In this post, I’d love to share with you some code examples that show how to validate query parameters of API requests with Spring Boot starter validation and Jakarta validation constraints.

You know, a request can contain some query parameters in form of name=value pairs in the URI, for example:

GET /v1/students?pageSize=10&pageNum=2

This request has 2 query parameters: the first one is pageSize which has value 10 and the second one is pageNum which has value 2.

If the request method is POST, then the query parameters will be in the request body - also in form of name=value pairs.

And in Java code with Spring, we can bind parameters of a REST controller’s handler method directly to query parameters. For example:

@RestController
@RequestMapping("/api/v1/students")
public class StudentApiController {

	@GetMapping
	public ResponseEntity<?> list(int pageSize, int pageNum) {
		
		// code that uses query parameters pageSize and pageNum...
		
		// return response...
	}
}
Here, the method parameter pageSize is bound to the query parameter pageSize, and the same for the method parameter pageNum. If the method parameter and query parameter have different names, we can use the @RequestParam annotation like this:

@GetMapping
public ResponseEntity<?> list(@RequestParam("pageSize") int pSize, @RequestParam("pageNum") int pNum) {

	// ...
}
Also, the @RequestParam annotation should be used to indicate that a parameter is optional and has default value if not present. For example:

@GetMapping
public List<Student> list(
		@RequestParam(required = false, defaultValue = "10") int pageSize, 
		@RequestParam(required = false, defaultValue = "1") int pageNum) {
	
	//...
	
}
So, how to validate the values of the query parameters pageSize and pageNum? Suppose that the API requires pageSize must be in the range of 10 to 50 and pageNum must be a positive number.



Certainly you can write code to check the values of the binding method parameters and return an error response in the handler method. But it’s strongly recommended to use Jakarta validation constraints with a custom exception handler.

Here are the steps to validate query parameters of API requests:

  • Add the dependency spring-boot-starter-validation to the project, if not available
  • Use Jakarta Bean/Hibernate Validator’s validation constraint annotations, such as @NotNull, @NotBlank, @Size, @Min, @Max, @Positive
  • Use @Validated annotation for the REST controller class
  • Customize error response in a global exception handler class
So, let’s update the mentioned controller class and the handler method as follows:

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Positive;

import org.springframework.validation.annotation.Validated;

@RestController
@RequestMapping("/api/v1/students")
@Validated
public class StudentApiController {

	@GetMapping
	public ResponseEntity<?> list(
			@Min(value = 10) @Max(value = 50) int pageSize, @Positive int pageNum) {

		// code that uses query parameters pageSize and pageNum...

		// return response...
	}
}
As you can see, we use the @Validated annotation for the controller class, so the specified constraint annotations will be called to validate the requests. And we use the @Min and @Max constraints for the query parameter pageSize, and @Positive constraint for pageNum.

Run the following curl command to test a negative case:

curl "localhost:8080/api/v1/students?pageSize=5&pageNum=0"

If no custom exception handler is configured, the server will return HTTP status code 500 (Internal Server Error) with the following error details in the response body:

{
  "timestamp": "2023-03-01T04:56:18.349+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "trace": "jakarta.validation.ConstraintViolationException:...",
  "message": "list.pageNum: must be greater than 0, 
  		list.pageSize: must be greater than or equal to 10",
  "path": "/api/v1/students"
}
However, what if we want to customize this error response? Such as sending the status code 400 without exception trace exposed to clients?

Here’s an example, code a custom global exception handler class as below:

package net.codejava;

import java.util.*;

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

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;

@ControllerAdvice
public class CustomExceptionHandler {


	@ExceptionHandler(ConstraintViolationException.class)
	@ResponseBody
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public Object handleRequestValidationException(Exception ex, HttpServletRequest request) {
		Map<String, Object> responseBody = new LinkedHashMap<>();
		
		responseBody.put("timestamp", new Date());
		responseBody.put("status", HttpStatus.BAD_REQUEST.value());
		responseBody.put("error", ex.getMessage());
		responseBody.put("path", request.getServletPath());
		
		return responseBody;
	}

}
Then, test the API using the same curl command above, you’ll see the error response is now updated:

< HTTP/1.1 400
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Wed, 01 Mar 2023 05:16:27 GMT
< Connection: close
<
{
  "timestamp": "2023-03-01T05:16:27.022+00:00",
  "status": 400,
  "error": "list.pageSize: must be greater than or equal to 10, list.pageNum: must be greater than 0",
  "path": "/api/v1/students"
}
You can also specify custom error messages for the validation constraints, as shown in the below example:

@GetMapping
public List<Student> list(
		@Min(value = 10, message = "Page size must greater than 9") 
		@Max(value = 50, message = "Page size must be less than 51") int pageSize, 
		@Positive(message = "Page number must be greater than 0") int pageNum) {
		
	//...
}
Then below is the response for a negative test case:

< HTTP/1.1 400
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Wed, 01 Mar 2023 05:29:58 GMT
< Connection: close
<

{
  "timestamp": "2023-03-01T05:29:58.815+00:00",
  "status": 400,
  "error": "list.pageNum: Page number must be greater than 0, list.pageSize: Page size must greater than 9",
  "path": "/api/v1/students"
}
And test the API for a positive case:

curl "localhost:8080/api/v1/students?pageSize=20&pageNum=1"

You should get HTTP status 200 (OK) with student information in the response body.

If the query parameters are optional, you should use the @RequestParam annotation like this:

@GetMapping
public List<Student> list(
		@RequestParam(required = false, defaultValue = "10") 
			@Min(value = 10) @Max(value = 50) Integer pageSize, 
			
		@RequestParam(required = false, defaultValue = "1") 
			@Positive Integer pageNum) {
	
	//...
}
Note that in this case, we use wrapper type Integer instead of primitive type int, to avoid conversion of nulls to primitive numbers.

Those are some code examples for validating query parameters of REST API requests with Spring Boot starter validation. I hope you find this article helpful. You can see the full list of Jakarta validation constraints here.

Watch the following video to see the coding in action:

 

Recommendation: 

 

Related REST API Tutorials:


About the Author:

is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.



Add comment