Putting Hypermedia Back in REST with JAX-RS

Version 6

    by Sebastian Daschner

     

    Implementing a fully-fledged hypermedia-driven REST API in your Java enterprise application enables more flexibility and resilience towards changes. As a positive side effect, the need to document every single REST resource is eliminated as well.

     

    Everybody is doing REST—at least everybody claims they are. But most web APIs you can find out there are something between "RPC style" and "basic resources with HTTP semantics"—except they are built without hypermedia.

     

    Real-World "REST" Examples

     

    Many real-world projects are mistakenly calling any kind of HTTP API "RESTful." Consider the example shown in Listing 1.

     

    POST /doSomeAction  
    <someActionRequest>
         <param>12345</param> 
    </someActionRequest>
    
    200 OK
    
    <someActionResponse>
         <value>2345</value> 
    </someActionResponse>
    

     

    Listing 1. RPC HTTP example (request and response)

     

    These HTTP calls in fact describe method calls over HTTP. The URL doSomeAction describes a method name, POST is used for all calls (even to read data), and the request and response bodies describe input and output parameters.

     

    You might conclude that this is nothing other than SOAP without the SOAP envelope.

     

    Resources and HTTP Semantics

     

    The URLs of a REST API represent the business entities of the application. This means you need to think in terms of objects rather than verbs when designing the API.

     

    For instance, for a user management module, the user objects are exposed via HTTP, as shown in Listing 2.

     

    GET /users  
    
    200 OK  
    
    <users>
         <user>
             <id>12345</id>
             <name>Duke</name>
             <motto>Java rocks!</motto>
         </user>
         <!-- some more users ... --> 
    </users>
    

     

    Listing 2. Resources example (request and response)

     

    The differences here are that the URLs reflect the business objects (the list of all users) and the correct HTTP method is used to read a resource.

     

    If the client wants to read all users and then follow each user's resource, it would need to know the logic for how to construct the URLs. Assuming the URL of a specific user is /users/12345, the client needs to implicitly concatenate the URL of all users with the ID of the corresponding object. This tightly couples the client to the logic, which should reside only on the server.

     

    A common misconception is that REST is somewhat about predictable URLs. In fact, readable URLs are nice to have, but more important is the fact that the server alone stays in control of the URLs and actively guides the client to the needed resources.

     

    How can you achieve that?

     

    Listing 3 shows another example of how a user is created using resources and HTTP semantics.

     

    POST /users
    
    <user>
         <name>Duke</name>
         <motto>JAX-RS rocks!</motto> 
    </user>  
    
    201 Created 
    Location: /users/12345
    

     

    Listing 3. Creating resources (request and response)

     

    The server would respond to the request with HTTP status 201 Created and a Location header field containing the URL of the newly created resource. The client uses this URL for later access to the specific user's resource. This no longer implies that the URL is to be constructed on the client side, and the server is in control of its URLs again.

     

    Enter Hypermedia

     

    Directing the client regarding where to access related resources is the point at which hypermedia steps in.

     

    Consider the example of the list of users again, but this time with resource links, as shown in Listing 4.

     

    GET /users  
    
    200 OK
    
    <users>
         <user>
             <name>Duke</name>
             <motto>Java rocks!</motto>
             <link rel="self" href="/users/12345"/>
         </user>
         <!-- some more users ... --> 
    </users>
    

     

    Listing 4. Resources with links example (request and response)

     

    The response no longer contains the IDs of the business objects rather than links to the corresponding resources. The client can follow these links without prior knowledge of the application's structure. The only knowledge needed is the self relation. This tells the client that the link is the resource of the containing object (the user) itself.

     

    As a consequence, the links are taken to direct the client to all needed locations, only with the knowledge of the relations. The rest is up to the server.

     

    Listing 5 shows this resource link approach with another aspect—this time with a book store example in JSON format.

     

    GET /books/12345  
    
    200 OK
    
    {
         "name": "Java",
         "author": "Duke",
         // + availability, price
         "_links": {
             "self": "/books/12345",
             "add-to-cart": "/shopping_cart"
         }
    }
    

     

    Listing 5. Resource with several links (request and response)

     

    The resource not only contains the link of the self relation but also a URL where the "add to shopping cart" functionality can be accessed.

     

    This implies another benefit of using hypermedia. That link would be included only if the user is allowed to add the book to the cart. Consider the book store example where books can be bought, but the books can be added to the user's shopping cart only if several preconditions match. This business logic (for example, add only books that are in stock) ideally resides only on the server to prevent redundant logic.

     

    Instead of duplicating the logic (show the shopping cart button only if the book is in stock) to the client, the server includes the add-to-cart link in the response body only if that logic applies. The client knows the add-to-cart relation and will display a fancy button and access that URL if the button is clicked.

     

    But how does the API user know which data must be sent to the add-to-cart URL? The action would not be made via an HTTP GET call rather than a POST with needed data, for example, the book ID, how many books should be added, and so on. This information must either be documented (that is, a description of how to access that URL) or included in the API via a more sophisticated hypermedia approach.

     

    There are several hypermedia-aware content types that allow different levels of control. See M. Amundsen's entry on H Factor.

     

    One hypermedia-aware content type is Siren. It not only allows you to define links but also so-called actions, describing how resources are accessed in ways other than by using simple GET calls.

     

    Listing 6 shows an example of a book with links and actions.

     

    GET /books/12345
    
    200 OK
    
    {
         "class": [ "book" ],
         "properties": {
             "isbn": "1-1234-5678",
             "name": "Java",
             "author": "Duke",
             "availability": "IN_STOCK",
             "price": 29.99
         },
         "actions": [
             {
                 "name": "add-to-cart",
                 "title": "Add Book to cart",
                 "method": "POST",
                 "href": "http://api.jamazon.example.com/shopping_cart", 
                 "type": "application/json",
                 "fields": [
                     { "name": "isbn", "type": "text" },
                     { "name": "quantity", "type": "number" }
                 ]
             }
         ],
         "links": [
             { "rel": [ "self" ], "href": "http://api.jamazon.example.com/books/1234" }     
         ] 
    }
    

     

    Listing 6. Siren book resource example (request and response)

     

    The client receives all the needed information to make the call for the add-to-cart functionality, including the URL, the HTTP method, the content type, and all required content data in the form of JSON properties. The only knowledge left is where the information for the isbn and quantity fields come from. This is business logic, which obviously must reside on the client.

     

    As a result, the client can navigate through the API in a self-exploratory fashion without any prior knowledge besides the domain model of the application. A lot of documentation can be saved here, because the "how to use" information is baked into the API itself.

     

    Besides that, using hypermedia also enables a higher flexibility for API changes, which the client is able to adapt to.

     

    In general, the bigger your API and the greater the variety of its consuming clients, the more it makes sense to make the REST API hypermedia driven.

     

    Enter Java EE

     

    JAX-RS is the standard for developing RESTful web services under the Java EE umbrella.

     

    Listings 7 and 8 show how a JAX-RS resource class that handles HTTP calls (shown in Listing 5) and an annotated plain old Java object (POJO) might look.

     

    @Path("books") 
    @Produces(MediaType.APPLICATION_JSON) 
    public class BooksResource {
    
         @Inject
         BookStore bookStore;
    
         @Context
         UriInfo uriInfo;
    
         @GET
         public List<Book> getBooks() {
             final List<Book> books = bookStore.getBooks();
    
             books.stream().forEach(u -> {
                 final URI selfUri = uriInfo.getBaseUriBuilder().path(BooksResource.class).path(BooksResource.class, "getBook").build(u.getId());
                 u.getLinks().put("self", selfUri);
             });
    
              return books;
         }
    
         @GET
         @Path("{id}")
         public Book getBook(@PathParam("id") final long id) {
             final Book book = bookStore.getBook(id);
    
             final URI selfUri = uriInfo.getBaseUriBuilder().path(BooksResource.class).path(BooksResource.class, "getBook").build(book.getId());
             book.getLinks().put("self", selfUri);
    
              // check if business logic applies
             if (bookStore.isAddToCartAllowed(book)) {
                 final URI addToCartUri = uriInfo.getBaseUriBuilder().path(CartResource.class).build();
                 book.getLinks().put("add-to-cart", addToCartUri);
             }
    
              return book;
         }
    
    }
    

     

    Listing 7. JAX-RS resource class

     

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Book {
    
         @XmlTransient
         private long id;
         private String name;
         private String author;
    
         @XmlElement(name = "_links")
         private Map<String, URI> links = new HashMap<>();
    
         // getters & setters
    }
    

     

    Listing 8. Book example POJO

     

    The getBooks resource handler calls the BookStore managed bean to retrieve the books, which are enriched with the corresponding URIs and returned to the client. Note that even for the application/json content type, Java Architecture for XML Binding (JAXB) annotations can be used to control the mapping of Java objects to JSON output. The major JSON mapping frameworks shipped with application servers take the JAXB annotations into account as well.

     

    The UriInfo functionality of JAX-RS is used to programmatically create the URIs with information derived directly from the JAX-RS classes. The path method concatenates the path pieces from the @Path annotations. The getBook method contains a path parameter that needs to be substituted with the actual ID of the book. This is done by calling the buildmethod with a corresponding number of arguments.

    Another helpful feature of the UriInfo component is the getBaseUriBuilder builder, which constructs the protocol, host, and ports depending on the given HTTP request. If the application server is proxied, for example, with Apache or NGINX, then the originally requested protocol, host, and port of the HTTP request and—therefore, the base URL of the proxy server—is taken to construct the URIs. This comes out of the box with JAX-RS and minimizes the amount of configuration and tight coupling of the Java EE application with its IT landscape.

     

    The getBook resource handler does basically the same thing only for one book, and it also includes the add-to-cart link if the business logic matches. This is an example of how hypermedia is used to control the client's behavior.

     

    Listings 9 and 10 show potential responses of the given example.

     

    GET /hypermedia-test/resources/books
    
    200 OK
    
    [
       {   "name": "Java",
         "author": "Duke",
         "_links": {
           "self": "http://localhost:8080/hypermedia-test/resources/books/1",
         }
       },
       {
         "name": "Hello",
         "author": "World",
         "_links": {
           "self": "http://localhost:8080/hypermedia-test/resources/books/2"  
    
       }
      }
    ]
    

     

    Listing 9. Books resource example

     

    GET /hypermedia-test/resources/books/1
    
    200 OK
    
    {
       "name": "Java",
       "author": "Duke",
       "_links": {
         "self": "http://localhost:8080/hypermedia-test/resources/books/1"
        "add-to-cart": "http://localhost:8080/hypermedia-test/resources/shopping_cart"
      }
    }
    

     

    Listing 10. Book resource example

     

    The more-sophisticated example using a content type, such as Siren, needs more information in the response. There are libraries, such as Siren4J, which provide functionality to create responses in the desired hypermedia content type.

     

    However, I'll show an approach using plain Java EE 7 without any external library to avoid additional dependencies and to keep the resulting deployment artifacts as small as possible. This means you either create your own Java classes containing fields according to the content type or you use a programmatic approach. JSONP offers functionality to programmatically create JSON structures with maximum control.

     

    Listing 11 shows how to programmatically create a book resource response using JSONP.

     

    @GET
    public JsonArray getBooks() {
         return bookStore.getBooks().stream().map(b -> {
             final URI selfUri = uriInfo.getBaseUriBuilder().path(BooksResource.class).path(BooksResource.class, "getBook").build(b.getId());
             final JsonObject linksJson = Json.createObjectBuilder().add("self", selfUri.toString()).build();
             return Json.createObjectBuilder()
                     .add("name", b.getName())
                     .add("author", b.getAuthor())
                     .add("_links", linksJson).build();
         }).collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add).build();
    }
    
    @GET
    @Path("{id}")
    public JsonObject getBook(@PathParam("id") final long id) {
         final Book book = bookStore.getBook(id);
    
         final URI selfUri = uriInfo.getBaseUriBuilder().path(BooksResource.class).path(BooksResource.class, "getBook").build(book.getId());
         final JsonObjectBuilder linksJson = Json.createObjectBuilder().add("self", selfUri.toString());
         if (bookStore.isAddToCartAllowed(book)) {
             final URI addToCartUri = uriInfo.getBaseUriBuilder().path(CartResource.class).build();
             linksJson.add("add-to-cart", addToCartUri.toString());
         }      return Json.createObjectBuilder()
                 .add("name", book.getName())
                 .add("author", book.getAuthor())
                 .add("_links", linksJson).build(); 
    }
    

     

    Listing 11. Book resource example using JSONP

     

    I'm using Java 8 language features such as streams, lambda expressions, and method handles to programmatically construct JsonObjects and JsonArrays of the retrieved POJOs. With this approach, the developer is in full control of how the response can look and what content type is used.

     

    In real-world projects, this functionality for how the business objects are mapped to a hypermedia response containing links and actions would be separated into a single component, such as a Contexts and Dependency Injection (CDI) managed bean, and reused in all REST resource classes within the project.

     

    More-complete examples can be found on my JAX-RS Hypermedia GitHub project. The examples include how to create hypermedia APIs with resource links only and how to include actions (realized via Siren) using different approaches.

     

    Conclusion

     

    That's all you need in a Java EE 7 application to create a hypermedia-driven REST API.

     

    The most important design choices upfront are which level of hypermedia it makes sense to apply, which HTTP content type to take, and whether it's reasonable to use a third-party dependency to create responses versus going with a "plain Java EE 7" approach.

     

    See Also

     

     

    About the Author

     

    Sebastian Daschner is a Java freelancer working as a consultant, software developer, and architect. He is enthusiastic about programming and Java EE and is participating in the JCP, serving in the JSR 370 Expert Group and hacking on various open source projects on GitHub. He has been working with Java for more than six years. Besides Java, Daschner is also a heavy user of Linux and container technologies such as Docker. He evangelizes computer science practices on his blog and on Twitter.

     

    Join the Conversation

     

    Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!