We started using Jersey/JAX-RS for internal REST endpoints that get used by our front-end code. Endpoints that have to return a result, always send JSON objects.
For deb
Is it good practice to use
@Produces("application/json")
on all JSON producing endpoints?
If your resource methods produce JSON as representation of your resources, they should be annotated with @Produces(MediaType.APPLICATION_JSON)
. As a result, the response will have a Content-Type header indicating the media type of the payload.
The @Produces annotation is also used for request matching: The JAX-RS runtime matches the media type sent in the Accept header with the media type defined in the @Produces annotation.
If you don't want to annotate every resource method in your application, you can annotate the resource classes instead. It will indicate that all methods defined in such class must produce JSON as representation of your resources.
The media type defined in the @Produces annotation indicates the media type that will be produced by the MessageBodyWriter instances registered in the application. Consider the following example:
@GET
@Produces(MediaType.APPLICATION_JSON)
public Foo getFoo() {
Foo foo = new Foo();
return Response.ok(foo).build();
}
Once the getFoo()
method is annotated with @Produces(MediaType.APPLICATION_JSON)
, JAX-RS will write the Foo
instance as a JSON document. It's done in a MessageBodyWriter implementation. If your application uses Jackson, for example, the JacksonJsonProvider will be used to convert Java objects to JSON documents.
You should always declare the @Produces
and @Consumes
annotations (either at the class level or method level) for the purpose of Content Negotiation and HTTP protocol correctness. Without these annotations, the result will be dependent on the client request and the default behavior of the server (which may be different across implementations), which leads to unpredictable and ambiguous results.
With these annotations, we advertise what media types we can produce and consume. On Retrieve (GET) requests, the client should send an Accept
header with the media type of the resource they expect back. And on Create requests (PUT, POST), the client should send a Content-Type
header telling the server what media type the data is that they are sending. If these headers don't match what the server is advertised to handle, then the client will get error responses back telling them what the problem is; with a Retrieve request and a non-matching Accept
header, the response will be a 406 Not Acceptable. With a Create request and a non-matching Content-Type
header, the response will be a 415 Unsupported Media Type.
This is how content negotiation works. And to make sure our server behaves as the clients expect, we should declare what we can handle on the server. The annotations do just this.
As you mentioned, when you left off the @Produces
, the client told you you needed to change the response type. This is because the result was that the Content-Type
response header was set to application/octet-stream
, which is what the answers here conclude. Clients use the Content-Type
header to determine how to handle the response.
That last example was for a Retrieve request. If we left off the @Consumes
on a Create endpoint, a lot of different things can go wrong. Take for example we have an endpoint that we want to accept JSON, so we create a POJO to map the JSON to.
@POST
public Response create(Customer customer) {}
For this to work, it is dependent on the client setting the Content-Type
header on the request to application/json
. But without the @Consumes
annotation, we are basically advertising this endpoint to be able to accept any media type, which is just ridiculous. The @Consumes
annotation acts like a guard saying "If you don't send the right type of data, you cannot pass". But since we don't have the guard, all data is allowed through, and the result is unpredictable, because depending on what the client sets the Content-Type
to, we don't know what MessageBodyReader
1 will handle the conversion from the entity body to Customer
. If the correct MessageBodyReader
is not chosen (the one that converts JSON to POPJOs), then most likely it will lead to an exception, and the client will get back a 500 Internal Server Error, which is not as specific as getting a 415 Unsupported Media Type.
1. See chapter 8 and 9 of the Jersey docs. It will explain how entity bodies are converted to Java objects (and vice versa) using MessageBodyReader
and MessageBodyWriter
, respectively.