Implementing content negotiation based on HTTP headers

Representational State Transfer (REST) applications can return different representations of resources. You can use content negotiation based on HTTP Accept headers to determine the content format that is used to exchange data between servers and clients.

About this task

Resources can represent data in different formats. You can implement content negotiation based on URLs, request parameters, or HTTP headers. This task describes content negotiation based on HTTP Accept headers for sending and receiving different data formats.

When a web browser makes a request, it sends information about what it is looking for to the server in headers. One of these headers is the Accept header. The Accept header tells the server what formats or MIME types that the client is looking for. You can use the HTTP Accept headers to determine the content format used to exchange data.

While the Accept header is not as visible as URLs or parameters, this header is a more flexible method of handling content negotiation. You can also use the HTTP Accept, Accept-Charset, Accept-Language, and Accept-Encoding headers to determine the type of content that is returned from the server.

Using HTTP Accept headers, you can assign degrees of quality to acceptable responses. For example, a client can indicate XML is the preferred response content type. However, if XML is not available, the client can accept JSON or plain text for the format.

For example, if the Accept header contains a value such as application/json; q=1.0, text/xml;q=0.5, application/xml;q=0.5, this value indicates that JSON is preferred but XML is acceptable as well.

In other methods of content negotiation, typically only one preferred response type exists. However, you can use the HTTP Accept headers to inform the service of all the possible types that are acceptable to the client in one request. Furthermore, the HTTP Accept and its related headers are part of the HTTP standard.

It is important to consider the following aspects when implementing content negotiation using HTTP headers. Some clients might send incorrect values and some services might not respect the Accept HTTP headers. Also, processing all the HTTP headers and calculating the optimal response is not as straightforward as requesting content based on a URL or a request parameter.

Procedure

  1. Add a @javax.ws.rs.core.Context javax.ws.rs.core.Request parameter to the resource method.
  2. Use the javax.ws.rs.core.Variant.VariantListBuilder object to build a list of possible response variants that the service supports.
  3. Test for an acceptable response.

    Use the javax.ws.rs.core.Request.selectVariant(java.util.List<Variant>variant) method to determine if you receive an acceptable response to send to the client. A list of possible response variants is passed into the method. A variant represents a media type, language, and encoding combination. See the javax.ws.rs.core.Variant and javax.ws.rs.core.Variant.VariantListBuilder API documentation for more information about building a list of variants.

    Based on the possible response variants and the incoming request HTTP headers, the selectVariant(java.util.List<Variant> variant) method returns the best possible response variant that matches the HTTP headers.

    The following example demonstrates using the @javax.ws.rs.core.Context annotation to inject the javax.ws.rs.core.Request type. The javax.ws.rs.core.Request type can help determine the optimal response variant. In this example, a Request object is passed in by the runtime environment when the resource method is called.
    @Path("/resources")
    public class Resource
    {
        @Path("{resourceID}")
        @GET
        public Response getResource(@PathParam("resourceID") String resourceID, @Context Request req)
        {
            /* This method builds a list of possible variants. */
            /* You must call Variant.VariantListBuilder.add() object before calling the Variant.VariantListBuilder.build() object. */
            List<Variant> responseVariants =
                     Variant
                         .mediaTypes(
                                     MediaType.valueOf(MediaType.APPLICATION_XML + ";charset=UTF-8"),
                                     MediaType.valueOf(MediaType.APPLICATION_XML + ";charset=shift_jis"),
                                     MediaType
                                         .valueOf(MediaType.APPLICATION_JSON))
                         .encodings("gzip", "identity", "deflate").languages(Locale.ENGLISH,
                                                                             Locale.FRENCH,
                                                                             Locale.US).add().build();
    
              /* Based on the Accept* headers, an acceptable response variant is chosen.  If there is no acceptable variant,
              selectVariant will return a null value. */
    
             Variant bestResponseVariant = req.selectVariant(responseVariants);
             if(bestResponseVariant == null) {
    
                 /* Based on results, the optimal response variant can not be determined from the list given.  */ 
    
                return Response.notAcceptable().build();
             }
             MediaType responseMediaType = bestResponseVariant.getMediaType();
    
             /* This instruction obtains the acceptable language and modifies the response entity. */
    
             Locale responseLocale = bestResponseVariant.getLanguage();
             if(responseMediaType.isCompatible(MediaType.APPLICATION_XML_TYPE)) {
                 return Response.ok(/* entity in XML format */).type(MediaType.APPLICATION_XML).build();
             } else if (responseMediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
                 	return Response.ok(/* The entity is  in JSON format */)
    
    				.type(MediaType.APPLICATION_JSON).build();
             }
             return Response.notAcceptable(Variant.mediaTypes(MediaType.APPLICATION_XML_TYPE,
    				 MediaType.APPLICATION_JSON_TYPE).add().build()).build();
         }
     }

Results

You have implemented content negotiation using Accept headers to determine the formats for resources that represent data.