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);
}