REST-API Different Content-Type on Error Response

10,385

Solution 1

User should always specify what content it's expecting with Accept header. It's you job to return the error that was thrown/caught on the server side in the format that was specified in Accept header. In spring as far as I know it could be achieved with a special mapper. Below you can find such mapper written in groovy to handle text/html.

import groovy.xml.MarkupBuilder
import org.springframework.http.HttpInputMessage
import org.springframework.http.HttpOutputMessage
import org.springframework.http.converter.AbstractHttpMessageConverter

import static org.springframework.http.MediaType.TEXT_HTML

class ExceptionResponseHTMLConverter extends AbstractHttpMessageConverter<ExceptionResponse> {
  ExceptionResponseHTMLConverter() {
    super(TEXT_HTML)
  }

  @Override
  boolean supports(Class clazz) {
    clazz.equals(ExceptionResponse)
  }

  @Override
  ExceptionResponse readInternal(Class clazz, HttpInputMessage msg) {
    throw new UnsupportedOperationException()
  }

  @Override
  void writeInternal(ExceptionResponse e, HttpOutputMessage msg) {
    def sw = new StringWriter()
    new MarkupBuilder(sw).error {
      error(e.error)
      exception(e.exception)
      message(e.message)
      path(e.path)
      status(e.status)
      timestamp(e.timestamp)
    }
    msg.body << sw.toString().bytes
  }
}

And ExceptionResponse class:

class ExceptionResponse {
  String error
  String exception
  String message
  String path
  Integer status
  Long timestamp
}

Solution 2

I was facing the same issue, and I was having the exact same question about the REST best practices.

All the articles I read about handling errors in API responses use JSON. Example here.

I don't think all of those APIs always wrap the data in JSON. Sometimes you just have to serve files, or text or non-json stuff... Also, I've stumbled upon RFC7807, which proposes a standard way to expose errors/probems with JSON format, even using its own content-type application/problem+json. Thus we can safely assume that using a different Content Type for HTTP 200 than for HTTP error codes is rather a good practice.

About how to do it with Spring Framework, it's actually very simple. Once you've understood that the "produces ={}" is basically a declarative way to say that your response will be of some type, you can imagine that it's also possible to programmatically set the type you want to return.

Here is an example API that should return application/octet-stream (a binary file).

@GetMapping(path = "/1/resources/hello", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public ResponseEntity<StreamingResponseBody> getFile(@RequestParam(value = "charset", required = false, defaultValue = "UTF-8") String charset) {
    return ResponseEntity.ok().body(outputStream -> outputStream.write("Hello there".getBytes(Charset.forName(charset))));
}

When it works, it will return a file with the right content-type. Now, if you want to handle the error case (in this case, a wrong charset parameter), you can create an Exception Handler:

@ExceptionHandler(UnsupportedCharsetException.class)
public ResponseEntity<?> handleCharsetException(UnsupportedCharsetException e) {
    return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON_UTF8).body(new ErrorResponse("1", "Wrong charset"));
}

And now, the error case also works as expected:

GET http://localhost/1/resources/hello?charset=CRAP

HTTP/1.1 400 Bad Request
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: application/json;charset=UTF-8
Date: Mon, 25 Mar 2019 17:37:39 GMT

{
  "code": "1",
  "message": "Wrong charset"
}
Share:
10,385

Related videos on Youtube

Vhaos
Author by

Vhaos

Updated on September 15, 2022

Comments

  • Vhaos
    Vhaos over 1 year

    Since some weeks I'm working on an rest api using spring-mvc. The REST-API is working properly and I`m almost done until one last problem when it comes to error handling with specific error-objects.

    The REST-API is using JSON as format to serialize Java-Objects. When an error occurs during service-execution an specific error-object gets created and sent back to the client.

    Everything is working fine when my rest-services are marked as "produces=application/json". But there are also some services which only need to return simple text using "produces=text/plain". When an error occurs in one of these services Spring-MVC will throw an HttpMediaTypeNotAcceptableException. Seems to be correct cause client asks for content-type "text/plain" but server response with "application/json".

    Can you tell me what's the correct solution for this problem?

    1. Only using JSON as response content-type and wrapping simple text always in an special class object. => Seems to me not really REST like, cause REST should support multiple content-types.

    2. Every service serving "text" will be marked as "produces=application/json;text/plain" and Client also need to send both in "accept-header". => When doing it this way the API seems to support two content-types for same resource. But that`s not right. Only in case of an error the API will return JSON, otherwise it will be always "text".

    Sounds for me like a really special REST question and couldn`t find related questions on this topic.

  • Vhaos
    Vhaos almost 9 years
    Thanks for the fast response! When I understand ur suggestion right u would write a custom MessageConverter which returns the error-object in String-Format, which would not result in an HttpMediaTypeNotAcceptableException. I dont thought about this option and think that it will be really useful in other situations but for my problem I dont think its fitting. The client should b eable to handle errors in a generic/common way (expecting it to always be json). I think its more a REST-Style question than a technical one. How to handle this in general in a rest api.
  • Opal
    Opal almost 9 years
    In general you should always return data or error in the format that was sent in Accept header. In your situation if the client really needs every response to be in JSON you can do it, however this API shouldn't be published since it behaves in unexpected way. It may input was helpful please upvote it.
  • Opal
    Opal almost 9 years
    You can also return response as text/plain in a parseable way.
  • Vhaos
    Vhaos almost 9 years
    I think ur answer would be one way to implement a workaround for my problem. In the end I believe that u r right when saying only to response the content-type the client sent in accept-header. So I wil change my API to always use JSON...
  • Roman Vottner
    Roman Vottner about 5 years
    ... we can safely assume that using a different Content Type for HTTP 200 than for HTTP error codes is rather a good practice Actually a client tells a server all of its capabilities via the Accept header. The server should chose one of the representation formats listed or return a 415 Unsupported Media Type error (-> content type negotiation). It could also return a default representation format, though a client might not be able to process it which thus causes interoperability issues.
  • t0r0X
    t0r0X about 4 years
    @nimai THX! I had this exact constellation you described in your answer (produces=APPLICATION_OCTET_STREAM_VALUE and error=APPLICATION_JSON_UTF8), and it almost drove me nuts.
  • realMarkusSchmidt
    realMarkusSchmidt over 3 years
    About changing the content type: That's not what RFC 7807 is suggesting, quite the opposite. E.g. it defines an XML data model in addition to the JSON model, and in Appendix B it recommends to embed problem details in other formats or translate the model into another format's conventions if an API is not using XML or JSON. Thanks though for pointing out this RFC!
  • nimai
    nimai over 3 years
    @realMarkusSchmidt I disagree there. The RFC advises indeed to use the same format (json or XML) but it still defines a different content-type than your regular api content-type for 'problems', and that was my point.