23 June, 2013

HATEOAS using Spring Framework

HATEOAS is a constraint of the REST application architecture which is very often ignored by developers. In many cases this is caused by the lack of support of frameworks used to build and expose RESTful services. In this post I will show how quickly we can add HATEOAS to RESTful web services using Spring Framework. Source code of the demo web application presented below are available for download, run it using mvn tomcat7:run command. Also you can read a nice presentation about why HATEOAS is important.

The Spring HATEOAS Library (currently in Release 0.6) provides API to help creating REST representations that follow the HATEOAS principle. The core problem it tries to address is link creation and representation assembly. Let's see the main steps required for enabling the HATEOAS for a RESTful web service:

  1. Add support for hypermedia information to exposed resources. This is done by inheriting resource's classes from ResourceSupport. As result you get the support for adding Link(s) to the resources. Below you can see the resource class representing an author:
    import org.springframework.hateoas.ResourceSupport;
    public class AuthorResource extends ResourceSupport {
        private int authorId;
        private String name;
        public AuthorResource() {
        public AuthorResource(int authorId, String name) {
            this.authorId = authorId;
            this.name = name;
        public int getAuthorId() {
            return authorId;
        public void setAuthorId(int authorId) {
            this.authorId = authorId;
        public String getName() {
            return name;
        public void setName(String name) {
            this.name = name;
    and this is an example of adding a link to it:
    AuthorResource resource = new AuthorResource(123, "Joshua Bloch");
    resource.add(new Link("http://localhost:8080/hateoas-demo/authors/123"));
    The Link value object follows the Atom link definition and consists of a rel and an href attribute.

  2. Building links. Spring Hateoas provides a ControllerLinkBuilder that allows to create links by pointing to controller classes:
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
    // or by pointing directly to a controller method
    The builder inspects the given controller class for its root mapping and it frees developer from ugly manual string concatenation code (protocol, hostname, port, servlet base, etc.).

  3. Encapsulate resource creation in a separate class. Spring Hateoas provides a ResourceAssemblerSupport base class that helps reducing the amount of code needed to be written for mapping from an entity to a resource type and adding respective links. The assembler can then be used to either assemble a single resource or an Iterable of them. You can see below the resource assembler for author resource:
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
    import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
    import org.springframework.stereotype.Component;
    public class AuthorResourceAssembler extends ResourceAssemblerSupport<Author, AuthorResource> {
        public AuthorResourceAssembler() {
            super(AuthorController.class, AuthorResource.class);
        public AuthorResource toResource(Author author) {
            // will add also a link with rel self pointing itself
            AuthorResource resource = createResourceWithId(author.getAuthorId(), author);
            // adding a link with rel books pointing to the author's books
            return resource;
        protected AuthorResource instantiateResource(Author author) {
            return new AuthorResource(author.getAuthorId(), author.getName());

  4. Exposing resources. This is achieved by writing the actual controller. Nothing special here. Just invoke the business logic services (in our case BookRepository) and map the business entities to their resource representations using resource assemblers:
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
    import java.util.List;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    public class AuthorController extends AbstractController {
        private BookRepository bookRepository;
        private AuthorResourceAssembler authorResourceAssembler;
        private BookResourceAssembler bookResourceAssembler;
        @RequestMapping(method = RequestMethod.POST)
        public ResponseEntity<Void> createNewAuthor(@RequestBody NewAuthor newAuthor) {
            Author author = bookRepository.createNewAuthor(newAuthor.getName());
            HttpHeaders headers = new HttpHeaders();
            return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
        @RequestMapping(method = RequestMethod.GET)
        public List<AuthorResource> getAuthors() {
            return authorResourceAssembler.toResources(bookRepository.findAuthors());
        @RequestMapping(value = "/{authorId}", method = RequestMethod.GET)
        public AuthorResource getAuthor(@PathVariable("authorId") int authorId) {
            return authorResourceAssembler.toResource(findAuthorAndValidate(authorId));
        @RequestMapping(value = "/{authorId}/books", method = RequestMethod.GET)
        public List<BookResource> getAuthorBooks(@PathVariable("authorId") int authorId) {
            return bookResourceAssembler.toResources(findAuthorAndValidate(authorId).getBooks());
        private Author findAuthorAndValidate(int authorId) {
            Author author = bookRepository.findAuthor(authorId);
            if (author == null) {
                throw new ResourceNotFoundException("Unable to find author with id=" + authorId);
            return author;

  5. Testing. Let's eat our own dog food:
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertTrue;
    import java.util.List;
    import org.junit.Ignore;
    import org.junit.Test;
    import org.springframework.hateoas.Link;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.client.RestTemplate;
    public class AuthorControllerTest {
        private static final String BASE_URI = "http://localhost:8080/hateoas-demo";
        public void createNewAuthor() {
            RestTemplate restTemplate = new RestTemplate();
            // creating new author
            NewAuthor newAuthor = new NewAuthor("Brian Goetz");
            ResponseEntity<Void> response = restTemplate.postForEntity(BASE_URI + "/authors", newAuthor, Void.class);
            assertEquals(HttpStatus.CREATED, response.getStatusCode());
            // retrieving the newly created author details using the URI received in Location header
            AuthorResource author = restTemplate.getForObject(response.getHeaders().getLocation(), AuthorResource.class);
            assertEquals(newAuthor.getName(), author.getName());
            assertTrue(author.getAuthorId() > 0);
            // getting the author's books using the link with rel books
            Link authorBooksLink = author.getLink("books");
            List<BookResource> authorBooks = restTemplate.getForObject(authorBooksLink.getHref(), List.class);

  6. How it looks like: (output formatted using JSON Formatter Chrome extension)
  7. That's it, hyperlink it!


  1. We're using MockMvc to test our services rather that using restTemplate:

    private WebApplicationContext ctx

    private MockMvc mockMvc

    def setup( ) {
    mockMvc = MockMvcBuilders.webAppContextSetup( ctx ).build()


    String result = mockMvc.perform( get( "/people/1" ).accept( PersonServiceMediaType.PERSONV1JSON ) )
    .andDo( print() )
    .andExpect( status().isOk() )
    .andExpect( content().contentType( PersonServiceMediaType.PERSONV1JSON ) )

    def parsedResult = JSONValue.parse( result )

    assert parsedResult.forename == 'Hugh'
    assert parsedResult.dateOfBirth == '1977-03-29'

    1. Yes, you can test them using MockMvc also. We are using MockMvc in our integration tests and RestTemplate in our acceptance tests.

  2. Nice article. Very clear and concise. Look forward to trying your example.

  3. nice job! simple and to the point.

  4. Hi,
    I read your article.Its clean and precise. I have a question in connection to this.
    Please see the below Response

    {"enrollment": {"optionId": "AETNA"
    , "actions": [{"method": "PUT", "uri": "/12345/54321/processes/111222/
    , "dependentParticipation":[
    {"dependentId": "1001", "enrolled":true ,"effectiveStartDate":"2010/04/04", "effectiveEndDate":"999/12/31"}
    ,{"dependentId": "1002", "enrolled":true ,"effectiveStartDate":"2010/04/04", "effectiveEndDate":"999/12/31"}

    I am able to get the above output without the actions. Can i get that actions inserted in the output without creating an actions object.

  5. This comment has been removed by the author.

  6. i geeting this compile time error
    Type mismatch: cannot convert from ControllerLinkBuilder to Link

  7. YouTeam helps use blockchain banking technology to build a global payment network. See how it can achieve lower cost, quicker settlement & fewer exceptions.

  8. Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would request, wright more blog and blog post like that for us. Thanks you once agianMarriage certificate in delhi
    Marriage certificate in ghaziabad
    Marriage registration in gurgaon
    Marriage registration in noida
    special marriage act
    Marriage certificate online
    Marriage certificate in mumbai
    Marriage certificate in faridabad
    Marriage certificate in bangalore
    Marriage certificate in hyderabad thanks once again to all.

  9. This is Very very nice article. Everyone should read. Thanks for sharing. Don't miss WORLD'S BEST GAMES

  10. I can’t believe focusing long enough to research; much less write this kind of article. You’ve outdone yourself with this material without a doubt. It is one of the greatest contents. Managed IT Services Markham

  11. Wow! this is Amazing! Do you know your hidden name meaning ? Click here to find your hidden name meaning