1

I'm writing REST-API using Spring MVC framework.
I'm using bean-validation like:

class Person {
    @NotNull
    String name;
    @NotNull
    String email;
    @Min(0)
    Integer age;
}

I'm validating Person using @Valid annotation into controllers:

@PostMapping
public Person create(@Valid @RequestBody Person person) {return ...;}

To make errors human-readable I use spring's top level error handler:

@ControllerAdvice
public class CustomExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody String handle(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors().stream()
                .map(this::buildMessage)
                .collect(Collectors.toList());
        return errors.toString();
    }

    private String buildMessage(FieldError fe) {
        return fe.getField() + " " + fe.getDefaultMessage();
    }
}

So my errors looks like: [name may not be null, email may not be null]

Now I need to use language independent error code that will be parsed by different UIs to implement i18n.
Is there any way to build full error code? (that contains field name)

I see the following solutions:

  1. Use custom message every time I used annotation (ugly):

    class Person {
        @NotNull(message="app.error.person.name.not.null")
        String name;
        @NotNull(message="app.error.person.email.not.null")
        String email;
        @Min(0)(message="app.error.person.age.below.zero")
        Integer age;
    }
    
  2. Build correct code into my exception handler(don't know how):

    private String buildMessage(FieldError fe) {
        return "app.error." +
                fe.getObjectName() + "." +
                fe.getField() + "." +
                fe.getDefaultMessage().replaceAll("\\s", "");//don't know how to connect to concrete annotation
    }
    

    so message will be like app.error.person.name.maynotbenull

  3. Re-write all annotations and validators for them to build correct message by removing default ConstraintViolation and adding custom (overhead)

1 Answer 1

2

No need to specify message inside annotation . It will be an overhead

@ControllerAdvice
public class CustomExceptionHandler {

  @Autowired
  MessageSource messageSource;

  @ExceptionHandler(MethodArgumentNotValidException.class)
  @ResponseBody String handle(MethodArgumentNotValidException ex) {
    List<String> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(this::buildMessage)
            .collect(Collectors.toList());
    return errors.toString();
  }

  private String buildMessage(FieldError fe) {
    StringBuilder errorCode = new StringBuilder("");
    String localizedErrorMsg = "";
    errorCode.append("error").append(".");
    errorCode.append(fe.getObjectName()).append(".");
    errorCode.append(fe.getField()).append(".");
    errorCode.append(fe.getCode().toLowerCase());

        try {
            localizedErrorMsg = this.messageSource.getMessage(errorCode,(Object[]) null, LocaleContextHolder.getLocale());
        } catch (Exception ex) {
            localizedErrorMsg = fe.getDefaultMessage();
        }
    return localizedErrorMsg;
  }
}

And in the message file(i18n) use the following format

error.person.name.notnull = Name must not be null
error.person.email.notnull = Email must not be null
error.person.age.min= Minimum age should greater than 0.

Using this you don't have to write any message code in the annotation. Hopefully this will help.

Sign up to request clarification or add additional context in comments.

5 Comments

I don't need to localize my message, but ok, I can just throw away try block.
I want to ask in which case fe.getCode() will return null?
fe.getCode() will never return null. MethodArgumentNotValidException will be thrown because of that code only
but we have constructor for FieldError that set codes to null
In FiledError 2 constructor are available. If annotation is present it will call the second one which will set the value of code

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.