4

Currently I have a spring boot application and for validation of POJOs i am using javax validator.We have rest apis in the application.

My question is : How can I have separate error messages for separate fields of same type.

Explanation with example : In reference to the sample code below : If a1.name is not present have a separate error message (ERROR1) and if a2.name is not present have a separate error message (ERROR2)

My POJO, with the javax validator annotation looks something like :

public class A{ @NotNull private String name; @NotNull private String age; //..getters and setters } public class B{ @Valid private A a1; @Valid private A a2; } 

My rest controller looks like:

@RestController public class Controller1{ @GetMapping(value="/abc") public void api1(@Valid @RequestBody B b){...} } 

I tried to use group but javax @valid annotaion doesnt supports group. Another option i tried to use spring @Validated annotation but problem with that is it cant be applied on fields.

1 Answer 1

1

As far as I know, Bean validation groups are not designed to meet this use case.

If you call your REST service like that :

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET -d '{"a1":{}, "a2":{}}}' http://localhost:8080/abc 

The response is :

{ "timestamp":"2017-11-10T17:22:07.534+0000", "status":400, "error":"Bad Request", "exception":"org.springframework.web.bind.MethodArgumentNotValidException", "errors":[ { "codes":["NotNull.b.a1.name", "NotNull.a1.name", "NotNull.name", "NotNull.java.lang.String", "NotNull"], "arguments":[ { "codes":["b.a1.name", "a1.name" ], "arguments":null, "defaultMessage":"a1.name", "code":"a1.name" } ], "defaultMessage":"may not be null", "objectName":"b", "field":"a1.name", "rejectedValue":null, "bindingFailure":false, "code":"NotNull" }, { "codes":["NotNull.b.a2.age", "NotNull.a2.age", "NotNull.age", "NotNull.java.lang.String", "NotNull"], "arguments":[ { "codes":[ "b.a2.age", "a2.age"], "arguments":null, "defaultMessage":"a2.age", "code":"a2.age" } ], "defaultMessage":"may not be null", "objectName":"b", "field":"a2.age", "rejectedValue":null, "bindingFailure":false, "code":"NotNull" }, { "codes":[ "NotNull.b.a1.age", "NotNull.a1.age", "NotNull.age", "NotNull.java.lang.String", "NotNull"], "arguments":[ { "codes":["b.a1.age", "a1.age"], "arguments":null, "defaultMessage":"a1.age", "code":"a1.age" } ], "defaultMessage":"may not be null", "objectName":"b", "field":"a1.age", "rejectedValue":null, "bindingFailure":false, "code":"NotNull" }, { "codes":["NotNull.b.a2.name", "NotNull.a2.name", "NotNull.name", "NotNull.java.lang.String", "NotNull"], "arguments":[ { "codes":["b.a2.name", "a2.name"], "arguments":null, "defaultMessage":"a2.name", "code":"a2.name" } ], "defaultMessage":"may not be null", "objectName":"b", "field":"a2.name", "rejectedValue":null, "bindingFailure":false, "code":"NotNull" } ], "message":"Validation failed for object='b'. Error count: 4", "path":"/api/profile/abc" } 

If you look closely you'll see that the response says that you get four validation errors and, for each one, validation details like the constraint, object and targeted field, for example : NotNull.b.a1.name.

It's the responsability of the view (REST client) to interpolate this detailled response to display the appropriate validation messages.


Sometimes it's not an option to interpolate messages on the client side. In this case you can add a custom "validation errors" handler with Spring. Something like that :

@RestController public class Controller1 { // This assumes that Spring i18n is properly configured @Autowired private MessageSource messageSource; @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public Map<String, String> processValidationError(MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); List<FieldError> fieldErrors = result.getFieldErrors(); Map<String, String> errors = new HashMap<>(); for (FieldError fieldError: fieldErrors) { String fieldPath = fieldError.getField(); String messageCode = fieldError.getCode() + "." + fieldPath; String validationMessage = messageSource.getMessage(messageCode, new Object[]{fieldError.getRejectedValue()}, Locale.getDefault()); // add the validation message, for example "NotNull.a1.name" => "a should not be null" errors.put(fieldError.getField(), validationMessage); } return errors; } @GetMapping(value="/abc") public void api1(@Valid @RequestBody B b){ //... } } 
Sign up to request clarification or add additional context in comments.

2 Comments

Hi Sebastein Thanks a lot for your reply. I have already been using a solution that is somewhat similar to what you suggested. So I am maintaining a map of a [fieldName , message]. The fieldName contains the parentVariableName.fieldName. We can get this from the FieldError.getField(). And then using the fieldName we can get the respective message from the map. I was just eager to find out if javax validation has provided some easier way to handle the above explained scenario.
I see. Again, I think you will not find a solution with Bean validation only. But Spring Validation works at a higher level and provides a solution because you can override the default response returned when there is a validation error. You can therefore return a custom response that could be a map of the field paths as keys and validation messages as values.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.