18

I have a simple webservice that returns content either as json or as plain text (depending on the clients' accept http header).

Problem: if an error occurs during text/plain request, Spring somehow returns a 406 Not Acceptable. Which is kind of wrong, because spring could as well just write the error out as plain error text, and moreover should absolutely preserve the 400 error status:

@RestController
public class TestServlet {
    @PostMapping(value = "/test", produces = {APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE, "text/csv"})
    public Object post() {
        throw new BadRequestException("bad req");
    }
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
    public BadRequestException(String msg) {
        super(msg);
    }
}

POST request with accept=application/json:

{
    "timestamp": "2018-07-30T14:26:02",
    "status": 400,
    "error": "Bad Request",
    "message": "bad req",
    "path": "/test"
}

BUT with accept=text/csv (or text/plain) shows an empty response with status 406 Not Acceptable.

I also noticed the DispatcherServlet.processDispatchResult() is called twice: first with my BadRequest exception, 2nd time with HttpMediaTypeNotAcceptableException. So clearly the rendering of my custom exception fails, but why?

membersound
  • 66,525
  • 139
  • 452
  • 886
  • 3
    Look at this: https://jira.spring.io/browse/SPR-16318 It seams it is a known issue. – pinturic Aug 07 '18 at 13:18
  • 1
    Thanks, probably that's the cause... – membersound Aug 07 '18 at 13:35
  • Duplicate of https://stackoverflow.com/questions/47831530 – Stephen C Aug 11 '18 at 01:38
  • This is due to the default exception handling of Spring Boot. Which translates that into JSON and not text. Which is also why you see a double dispatch (first the error and then the error on converting the 400 to a response, resulting in a 406). It probably works if you disable the default exception handling in spring Boot. – M. Deinum Aug 14 '18 at 07:10

4 Answers4

1

The problem is the restrictive Accept header allowing only one content type as response. In case of an error, Spring MVC needs to handle the BadRequestException and produce the required content type using a registered HttpMessageConverter.

By default Spring Boot has no message converter to produce text/plain directly from any object. You may register an ObjectToStringHttpMessageConverter (as a bean should work for Spring Boot) to allow this and you will get the result of BadRequestException.toString() as response body.

I assume a similar problem for text/csv but I am not sure how your setup for CSV message conversion looks like.

Arne Burmeister
  • 18,467
  • 8
  • 50
  • 89
0

The condition written in "produces" determines the media type to use for the response to be "text/csv". So For a success scenario it works fine, **

but when you go for rendering an exception with a JSON body that becomes a problem and gives you a 406 instead.

**

Darshan Dalwadi
  • 656
  • 5
  • 10
0

in latest versions of spring framework the problem fixes, but in old versions,as mentioned in Spring JIRA comments you should remove HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribute from request the code might be like this :

@RestControllerAdvice
public class ExampleControllerAdvice {

    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<?> handleException(HttpServletRequest request, Exception e) {
        request.removeAttribute(
                  HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

        return new ResponseEntity<?>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
0

we can handle exception in advice

@ControllerAdvice
class ExceptionHandler{

@ExceptionHandler(value = {HttpMediaTypeNotAcceptableException.class})
public ResponseEntity handleMediaTypeException(HttpMediaTypeNotAcceptableException e) {
APIErrorResponse apiErrorResponse = new APIErrorResponse();
apiErrorResponse.setErrorCode("set custom code here");
apiErrorResponse.setErrorMessage("set custom meggage here/ here we can use message from object of exception i.e e.getMessage()");
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}


}
G.Khandal
  • 14
  • 1
  • 1