Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAX-RS — How to return JSON and HTTP status code together?

I'm writing a REST web app (NetBeans 6.9, JAX-RS, TopLink Essentials) and trying to return JSON and HTTP status code. I have code ready and working that returns JSON when the HTTP GET method is called from the client. Essentially:

@Path("get/id") @GET @Produces("application/json") public M_機械 getMachineToUpdate(@PathParam("id") String id) {      // some code to return JSON ...      return myJson; } 

But I also want to return an HTTP status code (500, 200, 204, etc.) along with the JSON data.

I tried to use HttpServletResponse:

response.sendError("error message", 500); 

But this made the browser think it's a "real" 500 so the output web page was a regular HTTP 500 error page.

I want to return an HTTP status code so that my client-side JavaScript can handle some logic depending on it (to e.g. display the error code and message on an HTML page). Is this possible or should HTTP status codes not be used for such thing?

like image 566
Meow Avatar asked Jan 14 '11 01:01

Meow


People also ask

How do I return a JSON object to a REST web service?

Create RESTEasy Web Service to Produce JSON with @BadgerFish Now create a class whose methods will be exposed to the world as web service. Use JBoss @BadgerFish annotation that supports to return response as JSON. To return JSON as response we need to use media type as application/json.

Which annotation is used for returning HTTP status code?

Returning Response Status Codes with @ResponseStatus This annotation takes as an argument, the HTTP Status Code, to be returned in the response.


2 Answers

Here's an example:

@GET @Path("retrieve/{uuid}") public Response retrieveSomething(@PathParam("uuid") String uuid) {     if(uuid == null || uuid.trim().length() == 0) {         return Response.serverError().entity("UUID cannot be blank").build();     }     Entity entity = service.getById(uuid);     if(entity == null) {         return Response.status(Response.Status.NOT_FOUND).entity("Entity not found for UUID: " + uuid).build();     }     String json = //convert entity to json     return Response.ok(json, MediaType.APPLICATION_JSON).build(); } 

Take a look at the Response class.

Note that you should always specify a content type, especially if you are passing multiple content types, but if every message will be represented as JSON, you can just annotate the method with @Produces("application/json")

like image 167
hisdrewness Avatar answered Sep 22 '22 21:09

hisdrewness


There are several use cases for setting HTTP status codes in a REST web service, and at least one was not sufficiently documented in the existing answers (i.e. when you are using auto-magical JSON/XML serialization using JAXB, and you want to return an object to be serialized, but also a status code different than the default 200).

So let me try and enumerate the different use cases and the solutions for each one:

1. Error code (500, 404,...)

The most common use case when you want to return a status code different than 200 OK is when an error occurs.

For example:

  • an entity is requested but it doesn't exist (404)
  • the request is semantically incorrect (400)
  • the user is not authorized (401)
  • there is a problem with the database connection (500)
  • etc..

a) Throw an exception

In that case, I think that the cleanest way to handle the problem is to throw an exception. This exception will be handled by an ExceptionMapper, that will translate the exception into a response with the appropriate error code.

You can use the default ExceptionMapper that comes pre-configured with Jersey (and I guess it's the same with other implementations) and throw any of the existing sub-classes of javax.ws.rs.WebApplicationException. These are pre-defined exception types that are pre-mapped to different error codes, for example:

  • BadRequestException (400)
  • InternalServerErrorException (500)
  • NotFoundException (404)

Etc. You can find the list here: API

Alternatively, you can define your own custom exceptions and ExceptionMapper classes, and add these mappers to Jersey by the mean of the @Provider annotation (source of this example):

public class MyApplicationException extends Exception implements Serializable {     private static final long serialVersionUID = 1L;     public MyApplicationException() {         super();     }     public MyApplicationException(String msg)   {         super(msg);     }     public MyApplicationException(String msg, Exception e)  {         super(msg, e);     } } 

Provider :

    @Provider     public class MyApplicationExceptionHandler implements ExceptionMapper<MyApplicationException>      {         @Override         public Response toResponse(MyApplicationException exception)          {             return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();           }     } 

Note: you can also write ExceptionMappers for existing exception types that you use.

b) Use the Response builder

Another way to set a status code is to use a Response builder to build a response with the intended code.

In that case, your method's return type must be javax.ws.rs.core.Response. This is described in various other responses such as hisdrewness' accepted answer and looks like this :

@GET @Path("myresource({id}") public Response retrieveSomething(@PathParam("id") String id) {     ...     Entity entity = service.getById(uuid);     if(entity == null) {         return Response.status(Response.Status.NOT_FOUND).entity("Resource not found for ID: " + uuid).build();     }     ... } 

2. Success, but not 200

Another case when you want to set the return status is when the operation was successful, but you want to return a success code different than 200, along with the content that you return in the body.

A frequent use case is when you create a new entity (POST request) and want to return info about this new entity or maybe the entity itself, together with a 201 Created status code.

One approach is to use the response object just like described above and set the body of the request yourself. However, by doing this you loose the ability to use the automatic serialization to XML or JSON provided by JAXB.

This is the original method returning an entity object that will be serialized to JSON by JAXB:

@Path("/") @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public User addUser(User user){     User newuser = ... do something like DB insert ...     return newuser; } 

This will return a JSON representation of the newly created user, but the return status will be 200, not 201.

Now the problem is if I want to use the Response builder to set the return code, I have to return a Response object in my method. How do I still return the User object to be serialized?

a) Set the code on the servlet response

One approach to solve this is to obtain a servlet request object and set the response code manually ourselves, like demonstrated in Garett Wilson's answer :

@Path("/") @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public User addUser(User user, @Context final HttpServletResponse response){      User newUser = ...      //set HTTP code to "201 Created"     response.setStatus(HttpServletResponse.SC_CREATED);     try {         response.flushBuffer();     }catch(Exception e){}      return newUser; } 

The method still returns an entity object and the status code will be 201.

Note that to make it work, I had to flush the response. This is an unpleasant resurgence of low-level Servlet API code in our nice JAX_RS resource, and much worse, it causes the headers to be unmodifiable after this because they were already sent on the wire.

b) Use the response object with the entity

The best solution, in that case, is to use the Response object and set the entity to be serialized on this response object. It would be nice to make the Response object generic to indicate the type of the payload entity in that case, but is not the currently the case.

@Path("/") @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public Response addUser(User user){      User newUser = ...      return Response.created(hateoas.buildLinkUri(newUser, "entity")).entity(restResponse).build(); } 

In that case, we use the created method of the Response builder class in order to set the status code to 201. We pass the entity object (user) to the response via the entity() method.

The result is that the HTTP code is 401 as we wanted, and the body of the response is the exact same JSON as we had before when we just returned the User object. It also adds a location header.

The Response class has a number of builder method for different statuses (stati ?) such as :

Response.accepted() Response.ok() Response.noContent() Response.notAcceptable()

NB: the hateoas object is a helper class that I developed to help generate resources URIs. You will need to come up with your own mechanism here ;)

That's about it.

I hope this lengthy response helps somebody :)

like image 44
Pierre Henry Avatar answered Sep 23 '22 21:09

Pierre Henry