Currently it is not that easy to get access to the injected MessageSource when extending the ResponseEntityExceptionHandler. It is in a private field and there is no getter for it. It would be handy if there would be an protected getMessageSource so that when subclassing getting the MessageSource is easier.

For now it is possible to workaround it by implementing MessageSourceAware as well and store the reference in the @ControllerAdvice and calling the super method. But it is far from ideal.

Affects: Spring 6.0.x

Comment From: rstoyanchev

Makes sense, I'll go ahead and add a getter.

Are you calling ErrorResponse.builder(..).build() and then updateAndGetBody to resolve "detail" and "title" message codes? We could add an overloaded build(MessageSource, Locale) to shorten that a bit as a follow-up improvement on #29462.

Comment From: mdeinum

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,                                                                                                                           HttpHeaders headers, HttpStatusCode status,WebRequest request) {
  var problemDetail = createProblemDetail(ex, status, "Validation errors", null, null, request);
  var errors = ex.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
  problemDetail.setProperty("errors", errors);
  return createResponseEntity(problemDetail, headers, status, request);
}

Is what I currently have. I also would like to use the MessageSource to resolve the locale specific erorr message for the FieldError. As that itself is a MessageSourceResolvable one could simply pass it to the MessageSource.getMessage. So I'm actually calling the methods provided from the ResponseEntityExceptionHandler.

I thought of using the ErrorResponse.builder but as I needed to call updateAndGetBody that let me down the path getting the MessageSource myself.

It is a sample from the Spring 6 Recipes book and I now wonder what the best practice would be?

Anyway having easy access to the MessageSource will at least be helpful in the case of the MethodArgumentNotValidException to resolve locale specific detailed error messages.

If you want I can provide a PR which adds a protected getMessageSource.

Comment From: rstoyanchev

Thanks for the extra detail.

The ErrorResponse builder is for exceptions that are not an ErrorResponse. MethodArgumentNotValidException is, and it already contains a ProblemDetail, so you can just change it, e.g. ex.getBody().setProperty(..). It actually exposes "detail" message arguments with the lists of global errors at {0} and the list of field errors at {1}. So, if that works for you, you can include those in the "detail" by adding a message for this exception.

There are also utility methods in MethodArgumentNotValidException to turn errors into a list of strings with a MessageSource. They are mainly used from WebExchangeBindException, but you could use them too. Now that I've added the getters, it looks like this:

protected ResponseEntity<Object> handleMethodArgumentNotValid(
        MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {

    var errors = MethodArgumentNotValidException.errorsToStringList(
            ex.getFieldErrors(), getMessageSource(), LocaleContextHolder.getLocale());

    ex.getBody().setProperty("errors", errors);

    return handleExceptionInternal(ex, null, headers, status, request);
}

It makes me wonder if we should expose similar, instance methods on MethodArgumentNotValidException, one for global and one for local fields, just taking a MessageSource and a Locale. This would support creating an "errors" property vs formatting them within the "detail" message as our current support aims to facilitate.

Comment From: mdeinum

Thanks for the hint on the helper methods on the MethodArgumentNotValidException and the reminder that it itself is a ErrorResponse, I should have looked at the javadoc for those. This code looks cleaner then what I had, will update my sample for the book.

I do like the idea of an errors property.

Comment From: rstoyanchev

Okay, thanks. I'll experiment with that.

Comment From: rstoyanchev

The above snippet can now be written using an instance method:

protected ResponseEntity<Object> handleMethodArgumentNotValid(
        MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {

    Map<ObjectError, String> errorMessages =
            ex.resolveErrorMessages(getMessageSource(), LocaleContextHolder.getLocale());

    ex.getBody().setProperty("errors", errorMessages.values());

    return handleExceptionInternal(ex, null, headers, status, request);
}