REST API Discoverability and HATEOAS

I just announced the release dates of my upcoming "REST With Spring" Classes:


Table of Contents

1. Overview

This article will focus on Discoverability of the REST API, HATEOAS and practical scenarios driven by tests.

2. Why make the API Discoverable

Discoverability of an API is a topic that doesn’t get enough well deserved attention, and as a consequence very few APIs get it right. It is also something that, if done correctly, can make the API not only RESTful and usable but also elegant.

To understand discoverability you need to understand that constraint that is Hypermedia As The Engine Of Application State (HATEOAS); this constraint of a REST API is about full discoverability of actions/transitions on a Resource from Hypermedia (Hypertext really), as the only driver of application state.

If interaction is to be driven by the API through the conversation itself, concretely via Hypertext, then there can be no documentation, as that would coerce the client to make assumptions that are in fact outside of the context of the API.

In conclusion, the server should be descriptive enough to instruct the client how to use the API via Hypertext only, which, in the case of a HTTP conversation, may be the Link header.

3. Discoverability Scenarios (Driven by tests)

So what does it mean for a REST service to be discoverable? Throughout this section, we will test individual traits of discoverability using Junit, rest-assured and Hamcrest. Since the REST Service has been previously secured, each test first need to authenticate before consuming the API.

3.1. Discover the valid HTTP methods

When a REST Service is consumed with an invalid HTTP method, the response should be a 405 METHOD NOT ALLOWED; in addition, it should also help the client discover the valid HTTP methods that are allowed for that particular Resource, using the Allow HTTP Header in the response:

public void
   // Given
   String uriOfExistingResource = restTemplate.createResource();

   // When
   Response res = givenAuth().post( uriOfExistingResource );

   // Then
   String allowHeader = res.getHeader( HttpHeaders.ALLOW );
   assertThat( allowHeader, AnyOf.<String> anyOf(
    containsString("GET"), containsString("PUT"), containsString("DELETE") ) );

3.2. Discover the URI of newly created Resource

The operation of creating a new Resource should always include the URI of the newly created resource in the response, using the Location HTTP Header. If the client does a GET on that URI, the resource should be available:

public void whenResourceIsCreated_thenUriOfTheNewlyCreatedResourceIsDiscoverable() {
    // When
    Foo newResource = new Foo(randomAlphabetic(6));
    Response createResp = givenAuth().contentType("application/json")
    String uriOfNewResource= createResp.getHeader(HttpHeaders.LOCATION);

    // Then
    Response response = givenAuth().header(HttpHeaders.ACCEPT, "application/json")

    Foo resourceFromServer = response.body().as(Foo.class);
    assertThat(newResource, equalTo(resourceFromServer));

The test follows a simple scenario: a new Foo resource is created and the HTTP response is used to discover the URI where the Resource is now accessible. The tests then goes one step further and does a GET on that URI to retrieve the resource and compares it to the original, to make sure that it has been correctly persisted.

3.3. Discover the URI to GET All Resources of that type

When we GET any particular Foo resource, we should be able to discover what we can do next: we can list all the available Foo resources. Thus, the operation of retrieving an resource should always include in its response the URI where to get all the resources of that type, again making use of the Link header:

public void whenResourceIsRetrieved_thenUriToGetAllResourcesIsDiscoverable() {
    // Given
    String uriOfExistingResource = createAsUri();

    // When
    Response getResponse = givenAuth().get(uriOfExistingResource);

    // Then
    String uriToAllResources = HTTPLinkHeaderUtil
      .extractURIByRel(getResponse.getHeader("Link"), "collection");

    Response getAllResponse = givenAuth().get(uriToAllResources);
    assertThat(getAllResponse.getStatusCode(), is(200));

Note that the full low level code for extractURIByRel – responsible for extracting the URIs by rel relation is shown here.

This test covers the thorny subject of Link Relations in REST: the URI to retrieve all resources uses the rel=”collection” semantics.

This type of link relation has not yet been standardized, but is already in use by several microformats and proposed for standardization. Usage of non-standard link relations opens up the discussion about microformats and richer semantics in RESTful web services.

4. Other potential discoverable URIs and microformats

Other URIs could potentially be discovered via the Link header, but there is only so much the existing types of link relations allow without moving to a richer semantic markup such as defining custom link relations, the Atom Publishing Protocol or microformats, which will be the topic of another article.

For example the client should be able to discover the URI to create new Resources when doing a GET on a specific Resource; unfortunately there is no link relation to model create semantics. Luckily it is standard practice that the URI for creation is the same as the URI to GET all resources of that type, with the only difference being the POST HTTP method. Forms can also be used to achieve this.

5. Conclusion

We have seen how a REST API is fully discoverable from the root and with no prior knowledge – meaning the client is able to navigate it by doing a GET on the root. Moving forward, all state changes are driven by the client using the available and discoverable transitions that the REST API provides in representations (hence Representational State Transfer).

This article covered the some of the traits of discoverability in the context of a REST web service, discussing HTTP method discovery, the relation between create and get, discovery of the URI to get all resources, etc.

The implementation of all these examples and code snippets can be found in my github project – this is an Eclipse based project, so it should be easy to import and run as it is.

Sign Up and get 25% Off my upcoming "REST With Spring" classes on launch:


, ,

  • Eugen Paraschiv

    This is an interesting point you raise – and I don’t think there’s any perfect solution to this problem. I have however implemented a few approaches and have seen others work as well.
    In your case, first – a few notes:
    – when you create a user, is should be a POST on /users or similar – not sure that /username is the best URI for this
    – supporting OPTIONS requests should let you know that you can do POST on the /users URI – to figure out that it supports creation of a new user
    One option is to go beyond the standard rel types and do something like: rel=”create” and rel=”edit” to signal to the user that this is a create URI.
    So a developer, consuming the service, coudl do an OPTIONS on /users, see that it supports POST, and then send a POST with a new user and get back a 201 Created with the Location header set to the new URI of the user.

    Hope this helps.


    • Pierre Primot


      and what if only a subset of the attributes of the user can be modified depending on the authorization of the connected user ?
      I thought hateoas was about returning the state of the resource and the allowed “links”, actions (avoiding the need of the OPTIONS roundtrip).
      I see the hateoas response as a html page that contains the data, the possible actions (hyperlinks,forms), but without formatting elements.
      Am I wrong ?

      • Eugen Paraschiv

        You’re definitely not wrong. The OPTIONS request is not part of that conversation – it’s just a helper. The main conversation should indeed be including the available actions in the response – and that’s what the tests in the article show. Now – if the format in which that’s done is via the Link header or something else (well established hypermedia types or a custom hypermedia type of your own) – that’s another question, but the high level conversation has the same goals.
        One quick side-note about OPTIONS is that there’s no reason why you cannot tune the response to that request according to the credentials the user provides – so basically an admin-type user sending an OPTIONS request can get a larger set of available actions than a non-admin type user.

  • Stephane

    In 3.3 what is the difference between a Foo instance and a Foo resource ?

    • Eugen Paraschiv

      No difference – the language is just somewhat ambiguous – I’ll clarify the wording there – thanks.

  • vvarma

    nice article. you should also consider adding a bit about Interceptors.. found them to be a great tool for exception handling

  • Stephane

    Hi Eugen, I cannot see any pagination handling on this page and the table of contents link was implying.

  • Stephane

    I now found some pagination example at I shall have a look. Thanks !

    • Eugen Paraschiv

      Glad you found it Stephane – do let me know if you run into anything else. Cheers,