Bakus123
Bakus123

Reputation: 1399

Return content type for Error Responses based on produces condition of @RequestMapping

I have custom errors handling in my REST webservice. I have methods returning XML / JSON as response. Everything worked fine on SpringBoot version 2.0.9. But after migration to latest (2.2.4) my error handling tests fail:

Content type expected:<application/xml> but was:<application/json>
Expected :application/xml
Actual   :application/json

After research I found that it's related with upgrading Spring to Version 5.1. Docs:

Content Negotiation for Error Responses The produces condition of an @RequestMapping no longer impacts the content type of error responses.

How can I reproduce behavior from earlier versions of Spring in the latest? I just want to return the content type for errors as specified in produces condition.

Rest methods:

@PostMapping(path = "/{scriptName}", produces = { MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<Object> xmlMethod(@RequestParam("payload") String payload, @PathVariable("scriptName") String scriptName) {

    Object result = service.call(payload, scriptName);
    return ResponseEntity.ok(new JsonBuilder(result).getContent());
}

@PostMapping(path = "/{scriptName}", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Object> jsonMethod(@RequestParam("payload") String payload, @PathVariable("scriptName") String scriptName) {
    Object result = service.call(payload, scriptName);
    return ResponseEntity.ok(new JsonBuilder(result).getContent());
}

CustomRestExceptionHandler:

@ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {

        HTTPErrorDTO httpError = new HTTPErrorDTO(HttpStatus.NOT_FOUND, ex.getLocalizedMessage());
        return new ResponseEntity<>(httpError, new HttpHeaders(), httpError.getStatus());
    }

    ....
    // handlers for other exceptions
}

Error DTO:

@XmlRootElement(name = "Exception")
@XmlAccessorType(XmlAccessType.FIELD)
public class HTTPErrorDTO {

    private HttpStatus status;
    private String message;
    private List<String> errors;
}

Related topic: Spring mvc - Configuring Error handling for XML and JSON Response

---EDIT

I tried to add custom content negotiation configuration. But one of clients of my REST API send me content-type = "application/x-www-form-urlencoded" and expect application/xml and don't send any accept headers.

So I can only decide what format the content type should be at the method/controller level.

Can I somehow inform exception handler from controller, which one content type should be set?

Upvotes: 3

Views: 3708

Answers (2)

Tomoki Sato
Tomoki Sato

Reputation: 638

You can pass a HandlerMethod object to an exception handler method (i.e. handleResourceNotFoundException in your question).

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(
    ResourceNotFoundException ex, 
    WebRequest request, 
    HandlerMethod handlerMethod) {
    ........        
}

The HandlerMethod object represents the controller method that raised the exception. So you can get values of produces from an @RequestMapping annotation on the object.

RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
String[] produces = requestMapping.produces();
....

In your source code of the question, produces is an array that has an element (i.e. MediaType.APPLICATION_XML_VALUE or MediaType.APPLICATION_JSON_VALUE).

See Also
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-exceptionhandler-args

Upvotes: 2

cassiomolin
cassiomolin

Reputation: 130957

In your CustomRestExceptionHandler, you can set a media type for the ResponseEntity.

return ResponseEntity.status(httpError.getStatus().value())
        .contentType(MediaType.APPLICATION_XML)
        .body(httpError);

Upvotes: 1

Related Questions