Custom Javax Annotation Error Handling in Spring
Kamer Elciyar
Posted on February 15, 2020
Hello! In this article, I’m gonna cover how to override default exception handler method for MethodArgumentNotValidException
which is thrown when Java Bean Validation Annotations are violated.<!--more-->
What is Java Bean Validation?
Java Bean Validation is a specification that is defined in JSR-380. It makes possible defining constraints with annotations, write custom constraints, etc. I will use Hibernate Validator since it is the only certified implementation of Bean Validation 2.0. It’s not a requirement to use with Spring but I’m going to implement it with Spring Boot because of its popularity. I will not explain how to use these annotations however you can find it in one of my articles.
Create a Simple Project and Provide Some Annotations
Create a project with Spring Initializr and select Web and Lombok dependencies. Because web-starter includes Hibernate Validator. Then create a class named User as below.
@Data
public class User {
@Length(min = 2, max = 30, message = "Name must be between 2-30 characters. ")
@Pattern(regexp = "^[a-zA-Z]+(([',. -][a-zA-Z])?[a-zA-Z]*)*$", message = "Name is invalid.")
private String name;
@Length(min = 2, max = 30, message = "Surname must be between 2-30 characters.")
@Pattern(regexp = "^[a-zA-Z]+(([',. -][a-zA-Z])?[a-zA-Z]*)*$", message = "Surname is invalid.")
private String surname;
@Email(message = "Enter a valid email address.")
private String email;
Using an external message source for error messages is better but I didn’t use it because it would make this article longer.
Then create a RestController and accept User as a @Valid
input.
@RestController
@RequestMapping("/api/users")
public class UserRestController {
@PostMapping
public ResponseEntity<User> saveUser(@Valid @RequestBody User user) {
return ResponseEntity.ok(user);
}
}
Do not forget to use
@Valid
annotation. Because it makes sure that the annotated parameter is validated.
Run the application and send an example request. (You don’t have to use cURL of course. I sent this request with Postman. You can just copy this and import it as a raw text to Postman.)
curl --location --request POST 'http://localhost:8080/api/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Marion",
"surname": "Cotillard",
"email": "marion@cotillard.com",
"birthdate": "1975-09-30"
}'
This request will return 200 as could be expected. Now let’s break some rules:
curl --location --request POST 'http://localhost:8080/api/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "M",
"surname": "Cotillard",
"email": "marion@cotillard.com",
"birthdate": "1975-09-30"
}'
I just broke @Length
rule on name
field. This request responded with a long response:
{
"timestamp": "2020-02-01T21:27:06.935+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Length.user.name",
"Length.name",
"Length.java.lang.String",
"Length"
],
"arguments": [
{
"codes": [
"user.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
},
30,
2
],
"defaultMessage": "Name must be between 2-30 characters. ",
"objectName": "user",
"field": "name",
"rejectedValue": "M",
"bindingFailure": false,
"code": "Length"
}
],
"message": "Validation failed for object='user'. Error count: 1",
"path": "/api/users"
}
This can be a confusing response but it’s possible to create a custom response.
Override Exception Handler
Firstly, create a POJO for the custom response.
@Getter
@Setter
@Builder
@AllArgsConstructor
public class CustomFieldError {
private String field;
private String message;
}
You can add extra fields but this is enough for this article. Then create an exception handler method:
@ExceptionHandler(MethodArgumentNotValidException.class)
public final ResponseEntity<Object> handleUserMethodFieldErrors(MethodArgumentNotValidException ex, WebRequest request) {
// some logic
}
Javax Annotations throws MethodArgumentNotValidException.class
so, overrode this exception. In the method, extract field errors and create CustomFieldError
objects from them.
final List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
final List<CustomFieldError> customFieldErrors = new ArrayList<>();
for (FieldError fieldError : fieldErrors) {
final String field = fieldError.getField();
final String message = fieldError.getDefaultMessage();
final CustomFieldError customFieldError = CustomFieldError.builder().field(field).message(message).build();
customFieldErrors.add(customFieldError);
}
You can also get rejectedValue
and errorCode
. Then create an HTTP response and return.
return ResponseEntity.badRequest().body(customFieldErrors);
Run the project again and send the same request. This will return 400 response with the body below:
[
{
"field": "name",
"message": "Name must be between 2-30 characters. "
}
]
You can customise this response or override similar exception handlers with the same approach.
Github Repo: https://github.com/kamer/custom-javax-annotation-error-handling
For questions, suggestions or corrections feel free to reach me on:
Email: kamer@kamerelciyar.com
Twitter: https://twitter.com/kamer_ee
Posted on February 15, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.