Issue:
I am looking for your help to figure out an issue I am getting. I have designed an ErrorRenderer.class to handle wrong API paths (HTTPStatus.NOT_FOUND exceptions) and return a custom response like this:
{"title": "NOT FOUND","status": 404,"message": "Given path '/service/abcd' does not exist.."}
but instead of getting a custom response, I am getting the below response (spring default stack trace) when API path is wrong:
{"cause": null,"stackTrace": [ {"classLoaderName": "app","moduleName": null,"moduleVersion": null,"methodName": "lambda$handle$1","fileName": "ResourceWebHandler.java","lineNumber": 431,"className": "org.springframework.web.reactive.resource.ResourceWebHandler","nativeMethod": false } ],"type": "about:blank","title": "Not Found","status": {"reasonPhrase": "Not Found","statusCode": 404 },"detail": "404 NOT_FOUND \"No static resource service/abcd.\"","instance": null,"parameters": {},"message": "Not Found: 404 NOT_FOUND \"No static resource service/abcd.\"","suppressed": [],"localizedMessage": "Not Found: 404 NOT_FOUND \"No static resource service/abcd.\""}
Information about my classes:
I have given Highest Order to ErrorRenderer.class-
@Component@Order(-3)public class ErrorRenderer extends AbstractErrorWebExceptionHandler { public ErrorRenderer( ErrorAttributes errorAttributes, WebProperties.Resources resources, ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) { super(errorAttributes, resources, applicationContext); super.setMessageWriters(serverCodecConfigurer.getWriters()); super.setMessageReaders(serverCodecConfigurer.getReaders()); } @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } private Mono<ServerResponse> renderErrorResponse(ServerRequest request) { Map<String, Object> errorPropertiesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults()); HttpStatus status = HttpStatus.BAD_REQUEST; if (HttpStatus.NOT_FOUND.value() == (int) errorPropertiesMap.get("status")) { errorPropertiesMap.clear(); errorPropertiesMap.put("title", "NOT FOUND"); errorPropertiesMap.put("status", HttpStatus.NOT_FOUND.value()); errorPropertiesMap.put("message", String.format("Given path '%s' does not exist..", request.requestPath().toString())); status = HttpStatus.NOT_FOUND; } return ServerResponse.status(status) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(errorPropertiesMap)); }}
and there is a Config class for ErrorRenderer.class:
@Configurationpublic class WebPropertiesConfiguration { @Bean public WebProperties.Resources resources() { return new WebProperties.Resources(); }}
I have another class to handle other exceptions:
@ControllerAdvice@Slf4j@Order(-1)public class RestExceptionHandler implements HttpAdviceTrait, GeneralAdviceTrait, ValidationAdviceTrait { @ExceptionHandler({ServerWebInputException.class}) public Mono<ResponseEntity<Object>> error(ServerWebInputException e) { some code ..... return Mono.just(ResponseEntity.badRequest().body(message)); } @ExceptionHandler(ValidationException.class) public Mono<ResponseEntity<Problem>> validationEx( some code.... return create(exception, map(errorResponse, Status.FORBIDDEN), request); } @ExceptionHandler(WebClientResponseException.class) public Mono<ResponseEntity<Problem>> restException( some code.... } private Problem map(ErrorResponse response, Status status) { return Problem.builder() .withTitle(response.getCode().toLowerCase().replaceAll("_", " ")) .withDetail(response.getMessage()) .withStatus(status) .build(); }}
there is another Configuration class for RestExceptionHandler.class
@Slf4j@Configurationpublic class RestExceptionHandlerConfiguration { @Bean public ProblemModule problemModule() { return new ProblemModule(); } @Bean @Order(-2) public WebExceptionHandler problemExceptionHandler( ObjectMapper mapper, RestExceptionHandler problemHandling) { return new ProblemExceptionHandler(mapper, problemHandling); } @Bean public ConstraintViolationProblemModule constraintViolationProblemModule() { return new ConstraintViolationProblemModule(); }}
Routing Test:
I have written a routing test. Just to test I commented out RestExceptionHandler.class and see if the ErrorRenderer.class would get picked up or not and it gets picked up and the test passes. But since I need to find a way to have both RestExceptionHandler.class and ErrorRenderer.class together at the end (commenting out solution will not work). And when the RestExceptionHandler.class is present then the test fails as somehow the RestExceptionHandler.class is getting picked up before the ErrorRenderer.class even though I haveprovided the highest order to ErrorRenderer.class -> @Order(-3).
@Test void path() { webClient .get() .uri("/service/abcd") .exchange() .expectStatus() .isNotFound() .expectBody() .jsonPath("$.title") .isEqualTo("NOT FOUND") .jsonPath("$.status") .isEqualTo(404) .jsonPath("$.message") .isEqualTo("Given path '/service/abcd' does not exist.."); }
when the test is failing, I get this on the console (JSON path issue does not make much sense though, I guess important is log coming form AdviceTraits):
o.z.problem.spring.common.AdviceTraits : Not Found: 404 NOT_FOUND "No static resource service/abcd."At JSON path "$.status", value <{statusCode=404, reasonPhrase=Not Found}> of type <java.util.LinkedHashMap> cannot be converted to type <java.lang.Integer>java.lang.AssertionError: At JSON path "$.status", value <{statusCode=404, reasonPhrase=Not Found}> of type <java.util.LinkedHashMap> cannot be converted to type <java.lang.Integer> ......................
o.z.problem.spring.common.AdviceTraits : Not Found: 404 NOT_FOUND "No static resource service/abcd." - I believe, This is coming from RestExceptionHandler.class
change in application.properties:
I have also tried to add these into aplication.properties but no results:
org.springframework.reactive.function.server.RouterFunctions=INFOserver.error.whitelabel.enabled: false
Specifications:I am using Spring Boot 3.2 with Java 17.
Note:I recently upgraded spring boot version from 2.6 to 3.2, could this be the reason for the issue? custom response was working before with the older spring version but not anymore with the new spring version.
I could use some insight into what is going wrong here.
Thank you in advance.