After upgrading to Spring Boot version 3.3.2, I've noticed that the getErrorAttributes method from DefaultErrorAttributes is being executed twice for a single request. This behavior differs from version 3.3.0, where getErrorAttributes was executed only once.
Steps to Reproduce:
- Define the following Spring Boot configuration and classes:
public interface ExceptionHandlerCustomizer<T extends Throwable> {
boolean supports(Throwable throwable);
void customize(Map<String, Object> errorAttributes, T throwable);
}
@Slf4j
@RequiredArgsConstructor
public class ExceptionAttributesWrapper extends DefaultErrorAttributes {
private final List<ExceptionHandlerCustomizer> handlerCustomizers;
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
final var error = getError(request);
final var errorAttributes = super.getErrorAttributes(request, options);
handlerCustomizers.stream()
.filter(handler -> handler.supports(error))
.forEach(handler -> handler.customize(errorAttributes, error));
return errorAttributes;
}
@Getter
public enum ErrorAttribute {
STATUS("status"),
ERROR("error"),
ERROR_CODE("errorCode"),
DEVELOPER_MESSAGE("developerMessage"),
USER_MESSAGE("userMessage"),
VALIDATION_DETAILS("validationDetails"),
SOURCE_APP("sourceApp");
private final String value;
ErrorAttribute(String value) {
this.value = value;
}
}
}
@Configuration(proxyBeanMethods = false)
public class HttpExceptionHandlerConfiguration {
@Bean
ExceptionHandlerCustomizer<ConstraintViolationException> constraintViolationExceptionHandler() {
return new ConstraintViolationExceptionHandlerCustomizer();
}
@SuppressWarnings("rawtypes")
@Bean
ErrorAttributes errorAttributes(List<ExceptionHandlerCustomizer> handlers) {
return new ExceptionAttributesWrapper(handlers);
}
}
@Slf4j
public class ConstraintViolationExceptionHandlerCustomizer implements ExceptionHandlerCustomizer<ConstraintViolationException> {
@Override
public boolean supports(Throwable throwable) {
return ConstraintViolationException.class.isAssignableFrom(throwable.getClass());
}
@Override
public void customize(Map<String, Object> errorAttributes, ConstraintViolationException throwable) {
String errorName = throwable.getClass().getSimpleName();
String errorMessage = ExceptionUtils.getRootCause(throwable).getMessage();
log.debug("Caught an instance of: {}, err: {}", errorName, errorMessage);
errorAttributes.replace(ExceptionAttributesWrapper.ErrorAttribute.STATUS.getValue(), 400);
errorAttributes.put(ExceptionAttributesWrapper.ErrorAttribute.ERROR.getValue(), errorName);
errorAttributes.put(ExceptionAttributesWrapper.ErrorAttribute.DEVELOPER_MESSAGE.getValue(), errorMessage);
}
}
@RequestMapping(
method = RequestMethod.DELETE,
value = "/v1/test/pnr/{pnr}/sth/{sth}"
)
default Mono<ResponseEntity<Void>> testEndpoint(
@Size(min = 11, max = 11) @Parameter(name = "pnr", description = "PNR. ", required = true, in = ParameterIn.PATH) @PathVariable("pnr") String pnr,
@Parameter(name = "sth", description = "Sth. ", required = true, in = ParameterIn.PATH) @PathVariable("sth") String sth
) {
Mono<Void> result = Mono.empty();
exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED);
return result.then(Mono.empty());
}
- Deploy the application and trigger an error that would be handled by the
ConstraintViolationExceptionHandlerCustomizer. - Observe the logs. The
getErrorAttributesmethod is logged as being executed twice. - On debug, I can also observe that my breakpoint is being executed twice.
Environment: - Spring Boot Version: 3.3.2 - Java Version: Amazon Coretto 17.0.12
Comment From: philwebb
@bskorka Can you please upgrade to 3.3.3 to see if your issue has been fixed by #41995
Comment From: bskorka
@philwebb — yes, it helps! I updated our Spring Boot version on the morning of August 22nd. The 3.3.3 version was released in the evening of that day. All in all, it works properly now, thank you for that fix, and your input.