REST-API Different Content-Type on Error Response
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"
}
Related videos on Youtube
Vhaos
Updated on September 15, 2022Comments
-
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?
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.
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 almost 9 yearsThanks 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 almost 9 yearsIn 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 almost 9 yearsYou can also return response as
text/plain
in a parseable way. -
Vhaos almost 9 yearsI 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 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 theAccept
header. The server should chose one of the representation formats listed or return a415 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 about 4 years@nimai THX! I had this exact constellation you described in your answer (
produces=APPLICATION_OCTET_STREAM_VALUE
anderror=APPLICATION_JSON_UTF8
), and it almost drove me nuts. -
realMarkusSchmidt over 3 yearsAbout 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 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.