• Skip to main content
  • Skip to secondary menu
  • Skip to primary sidebar
  • Skip to footer
  • Home
  • Contact Us

All about code

  • Java
    • Spring
      • Spring Boot
      • Spring Cloud
    • Java Miscellaneous Tips
    • Java Design Patterns
  • Data Structures and Algorithms
    • Algorithms
    • Data Structures
  • Thoughts and Stories
  • AI

Brief and Easy Explanation of HATEOAS

coffee cup and saucer beverage tea 24961

Introduction

Hypermedia as the Engine of Application State (HATEOAS) states that every response should include links that tell the client what it can do next. Instead of relying on fixed URI patterns stored in code, the client discovers actions at runtime by following the links supplied by the server. This keeps the client decoupled from the server’s URI layout and simplifies evolution of the API.

Minimal HATEOAS JSON Example

Without HATEOAS a user representation looks like {“id”:7,”name”:”Ada”} The client must know the update endpoint /users/7 and the delete endpoint /users/7. With HATEOAS the same information becomes {“id”:7,”name”:”Ada”,”_links”:{“self”:{“href”:”/users/7″},”update”:{“href”:”/users/7″,”method”:”PUT”},”delete”:{“href”:”/users/7″,”method”:”DELETE”}}} The server has now advertised the next possible actions.

Spring Boot Implementation of HATEOAS

Add spring-boot-starter-hateoas to the project. Turn a plain entity into a RepresentationModel so that links can be attached.

@Entity
class Task extends RepresentationModel<Task> {
    @Id @GeneratedValue Long id;
    String title;
    boolean completed;
}

A controller method builds the response with WebMvcLinkBuilder.

@GetMapping("/tasks/{id}")
public EntityModel<Task> one(@PathVariable Long id){
    Task task = repo.findById(id).orElseThrow();
    task.add(linkTo(methodOn(TaskController.class).one(id)).withSelfRel());
    task.add(linkTo(methodOn(TaskController.class).all()).withRel("tasks"));
    if(!task.completed){
        task.add(linkTo(methodOn(TaskController.class).complete(id)).withRel("complete"));
    }
    return EntityModel.of(task);
}

The produced JSON is

{
  "id":3,"title":"Refactor tests","completed":false,
  "_links":{
    "self":{"href":"/tasks/3"},
    "tasks":{"href":"/tasks"},
    "complete":{"href":"/tasks/3/complete"}
  }
}

Assembler Pattern

Repeated link creation in every controller method violates DRY. A small assembler centralises the rules.

@Component
class TaskModelAssembler implements RepresentationModelAssembler<Task,EntityModel<Task>>{
    public EntityModel<Task> toModel(Task task){
        EntityModel<Task> model = EntityModel.of(task);
        model.add(linkTo(methodOn(TaskController.class).one(task.getId())).withSelfRel());
        model.add(linkTo(methodOn(TaskController.class).all()).withRel("tasks"));
        if(!task.completed){
            model.add(linkTo(methodOn(TaskController.class).complete(task.getId())).withRel("complete"));
        }
        return model;
    }
}

The controller now delegates the assembly.

@Autowired TaskModelAssembler assembler;

@GetMapping("/tasks/{id}")
public ResponseEntity<EntityModel<Task>> one(@PathVariable Long id){
    Task task = repo.findById(id).orElseThrow();
    return ResponseEntity.ok(assembler.toModel(task));
}

Collection Resources

Returning a list is handled with CollectionModel.

@GetMapping("/tasks")
public CollectionModel<EntityModel<Task>> all(){
    List<EntityModel<Task>> tasks = repo.findAll().stream()
        .map(assembler::toModel)
        .collect(Collectors.toList());
    return CollectionModel.of(tasks,
        linkTo(methodOn(TaskController.class).all()).withSelfRel());
}

The HAL representation becomes

{
  "_embedded":{
    "taskList":[{...}]
  },
  "_links":{
    "self":{"href":"/tasks"}
  }
}

Conclusion

Keep links relevant to the current state; a completed task should no longer advertise the complete action. Use IANA link relations such as self, collection, next, prev when they fit. Reserve custom relations for domain-specific actions. Treat URIs as opaque strings on the client side; never build them by concatenation. Version the API by adding new links or relations rather than changing existing ones.

Primary Sidebar

Social

  • Facebook
  • LinkedIn
  • Twitter

Archives

  • May 2026
  • April 2026
  • March 2026
  • August 2025
  • July 2025
  • June 2025
  • May 2025
  • April 2025
  • March 2025
  • December 2024
  • October 2024
  • August 2024
  • July 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • September 2023
  • May 2023
  • March 2023
  • January 2023
  • November 2022
  • September 2022
  • August 2022

Recent Posts

  • Better Avoid Spring Boot Lazy-initialization @PostConstruct Trap
  • How To Handle Large Datasets in Spring Boot
  • How to Log HTTP Incoming Requests in Spring Boot
  • How to Reliably Implement Post-Commit Actions in Spring
  • Brief and Easy Explanation of HATEOAS

TAGS

Algorithms Apache POI Backtracking Date Structures Dynamic Programming engineering Graphs Greedy Horror Stories HSSF Java Java New Features Java Principles Java tips real life Sorting Spring Boot Spring Cloud Strings Trees

Footer

Privacy Policy Cookie Policy Terms and Conditions