Enterprise Java

Java REST Swagger vs. HATEOAS

In modern RESTful API development, API documentation and hypermedia-driven APIs play a crucial role. Two key concepts that developers often use are Swagger and HATEOAS. While Swagger helps document APIs, HATEOAS enhances API discoverability by including hypermedia links. Let us delve into understanding Swagger vs HATEOAS in Java REST.

1. Understanding Swagger and HATEOAS

1.1 What is Swagger?

Swagger, now officially known as OpenAPI, is a framework used for API documentation, design, and testing. It provides a structured way to describe RESTful APIs using a machine-readable format, typically in JSON or YAML. Swagger offers several key components:

  • OpenAPI Specification (OAS) – A standardized format to describe API endpoints, parameters, responses, and authentication.
  • Swagger UI – A web-based interactive UI that allows developers to explore and test API endpoints.
  • Swagger Editor – A browser-based editor to design and document APIs using OpenAPI.
  • Swagger Codegen – A tool to generate client SDKs and server stubs in multiple programming languages based on OpenAPI specs.

By providing interactive API documentation, Swagger simplifies API consumption for developers, testers, and consumers.

1.1.1 What is swagger.yml file?

The swagger.yml file is a YAML-based OpenAPI specification that documents the Spring Boot CRUD API. It defines the API endpoints, HTTP methods, request and response structures, and associated parameters. The file includes metadata such as the API title, description, version, and server URL. Each endpoint is described with its respective path, method (GET, POST, DELETE), expected request body or query parameters, and response codes with corresponding descriptions. The schema section defines the structure of application entities, specifying field names, data types, and constraints.

Additionally, it integrates HATEOAS support by documenting links to related operations. This file ensures API consistency, improves developer understanding, and enables the automatic generation of interactive API documentation via Swagger UI.

1.2 What is HATEOAS?

HATEOAS (Hypermedia as the Engine of Application State) is a RESTful API principle that enhances API discoverability by embedding hypermedia links within responses. This approach allows clients to navigate and interact with an API dynamically without prior knowledge of its structure.

1.2.1 Key aspects of HATEOAS

  • Hypermedia-driven – API responses include hyperlinks to related resources, guiding clients on available actions.
  • Self-descriptive – Clients understand available operations dynamically from the response itself.
  • Reduces client-side coupling – Changes in API structure do not require modifications on the client side, making APIs more adaptable.

1.2.2 Common Hypermedia Formats

  • HAL (Hypertext Application Language) – Uses JSON or XML to structure hypermedia links.
  • Siren – A richer format supporting actions and entities.
  • JSON-LD – A linked data format using JSON to structure API responses.

For example, a HATEOAS-compliant API response for a user resource might look like this:

1
{"id":123,"name":"John Doe","email":"john.doe@example.com","links":[{"rel":"self","href":"https://api.example.com/users/123"},{"rel":"orders","href":"https://api.example.com/users/123/orders"}]}

1.3 Key Differences between Swagger and HATEOAS

AspectSwaggerHATEOAS
PurposeAPI documentation & testingEnhances API discoverability with links
FormatJSON/YAMLHypermedia (HAL, Siren, etc.)
Client GuidancePredefined documentationDynamic link-driven interactions
UsageUsed for designing, documenting, and generating API client/server codeUsed within APIs to provide dynamic navigation
API EvolutionChanges require updating documentation and client implementationClients adapt dynamically based on response links

2. Code Example

Visit Spring Initializr and generate the application required for this article.

2.1 Adding Project Dependencies

Include the following dependencies in the pom.xml file.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- Dependency for building a Spring Boot web application -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 
<!-- Dependency for Spring Data JPA to work with databases -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
 
<!-- H2 Database dependency for an in-memory database -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
 
<!-- SpringDoc OpenAPI dependency for Swagger UI documentation -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.0.2</version>
</dependency>
 
<!-- Spring Boot HATEOAS dependency for hypermedia-driven REST APIs -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

2.2 Entity Class

Create an entity model class to interact with the database repository. We will use Lombok to simplify the generation of getters and setters.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.model;
 
import jakarta.persistence.*;
import lombok.Data;
 
@Entity
@Table(name = "product_table")
@Data
public class Product {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private double price;
 
    public Product() {
    }
 
    public Product(String name, double v) {
        this.name = name;
        this.price = v;
    }
 
    // Getters and Setters.
}

2.3 Repository Class

Create a repository class responsible for interacting with the database and managing CRUD operations.

1
2
3
4
5
6
7
8
9
package com.example.repository;
 
import com.example.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

2.4 Controller Class

Create a controller class to handle client requests, communicate with the database, and return responses enriched with Atom links, commonly known as HATEOAS. This class will demonstrate all CRUD operations except the PUT method, which is intentionally left out for brevity, allowing learners to implement it themselves. Additionally, ensure that any new method added to the controller class is also mapped in the swagger.yml file.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.example.controller;
 
import com.example.model.Product;
import com.example.repository.ProductRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
 
import java.util.List;
import java.util.stream.Collectors;
 
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
 
@RestController
@Slf4j
public class ProductController {
 
    private final ProductRepository repository;
 
    public ProductController(ProductRepository repository) {
        this.repository = repository;
    }
 
    // GET endpoint to retrieve all products with HATEOAS links
    @GetMapping("/products")
    public CollectionModel<EntityModel<Product>> getAllProducts() {
        log.info("Getting all products");
         
        List<Product> products = repository.findAll();
 
        // Convert each product into an EntityModel with HATEOAS links
        List<EntityModel<Product>> models = products.stream()
                .map(product -> EntityModel.of(product,
                        linkTo(methodOn(ProductController.class).getProductById(product.getId())).withSelfRel(),
                        linkTo(methodOn(ProductController.class).deleteProduct(product.getId())).withRel("delete-product")
                ))
                .collect(Collectors.toList());
 
        // Return the collection of products with a self link
        return CollectionModel.of(models, linkTo(methodOn(ProductController.class).getAllProducts()).withSelfRel());
    }
 
    // GET endpoint to retrieve a single product by ID with HATEOAS links
    @GetMapping("/product/{id}")
    public EntityModel<Product> getProductById(@PathVariable Long id) {
        log.info("Get product by id= " + id);
         
        // Find the product by ID or throw an exception if not found
        Product product = repository.findById(id).orElseThrow(
                () -> new RuntimeException("Product Not found"));
 
        // Return the product as an EntityModel with HATEOAS links
        return EntityModel.of(product,
                linkTo(methodOn(ProductController.class).getProductById(id)).withSelfRel(),
                linkTo(methodOn(ProductController.class).deleteProduct(id)).withRel("delete-product"),
                linkTo(methodOn(ProductController.class).getAllProducts()).withRel("all-products")
        );
    }
 
    // POST endpoint to create a new product
    @PostMapping("/products")
    public ResponseEntity<EntityModel<Product>> createProduct(@RequestBody Product product) {
        // Save the new product to the database
        Product savedProduct = repository.save(product);
         
        // Return the saved product with its URI location
        return ResponseEntity.created(linkTo(methodOn(ProductController.class).getProductById(savedProduct.getId())).toUri())
                .body(EntityModel.of(savedProduct));
    }
 
    // DELETE endpoint to remove a product by ID
    @DeleteMapping("/product/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        // Delete the product from the database
        repository.deleteById(id);
         
        // Return a 204 No Content response
        return ResponseEntity.noContent().build();
    }
}

2.4.1 Code Explanation

The ProductController class is a Spring Boot REST controller that manages product-related API endpoints. It uses Lombok’s @Slf4j for logging and interacts with the ProductRepository to perform CRUD operations. The constructor initializes the repository.

The @GetMapping("/products") method retrieves all products, converts them into EntityModel objects with HATEOAS links, and returns them as a CollectionModel.

The @GetMapping("/product/{id}") method fetches a single product by ID, adding HATEOAS links for self, delete, and all-products endpoints.

The @PostMapping("/products") method saves a new product and returns a response with a Location header pointing to the newly created resource.

The @DeleteMapping("/product/{id}") method removes a product by ID and returns a 204 No Content response.

Each endpoint logs relevant information and follows RESTful best practices with HATEOAS support for enhanced API navigation.

2.5 Swagger YAML

Create a swagger.yml file to document the Spring Boot CRUD API for managing products. The file should be located in the src/main/resources/static directory.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
openapi: 3.0.1
info:
  title: Product API
  description: RESTful API for managing products with Swagger and HATEOAS.
  version: 1.0.0
servers:
    description: Local Development Server
paths:
  /products:
    get:
      summary: Get all products
      operationId: getAllProducts
      tags:
        - Product
      responses:
        "200":
          description: List of products
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Product"
    post:
      summary: Create a new product
      operationId: createProduct
      tags:
        - Product
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Product"
      responses:
        "201":
          description: Product created successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
  "/product/{id}":
    get:
      summary: Get product by ID
      operationId: getProductById
      tags:
        - Product
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: number
      responses:
        "200":
          description: Product details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
        "404":
          description: Product not found
    delete:
      summary: Delete product by ID
      operationId: deleteProduct
      tags:
        - Product
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Product deleted successfully
        "404":
          description: Product not found
components:
  schemas:
    Product:
      type: object
      properties:
        id:
          type: number
          example: 1
        name:
          type: string
          example: Laptop
        price:
          type: number
          format: double
          example: 799.99

This OpenAPI 3.0.1 specification defines a Product API designed to manage products using Swagger and HATEOAS principles. The API runs on a local development server at http://localhost:10090 and supports multiple endpoints for interacting with product data.

The /products endpoint supports a GET request to retrieve a list of products, returning an array of Product objects, and a POST request to create a new product, requiring a JSON request body. The /product/{id} endpoint allows fetching a specific product by ID via a GET request, returning a 404 response if the product is not found, and deleting a product using a DELETE request.

The API follows a structured response format in JSON, ensuring consistency and usability. The Product schema includes three properties: id (numeric), name (string), and price (double). API consumers can use this specification to integrate or test the Product API efficiently.

Keep in mind that if you are using Swagger annotations directly within the controller class, there is no need for the swagger.yml file. However, while annotations work well for small to medium-sized projects, using a YAML file is a better approach for large-scale microservices projects, as it allows API documentation to be managed separately.

2.6 Application properties

Add the following properties to the src/main/resources/application.properties file.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
spring.application.name=springboot-hateoas-swagger
server.port=10090
 
# enable h2
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
 
# database configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
 
# hibernate Settings
spring.jpa.show-sql=true
## format SQL for better readability
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.defer-datasource-initialization=true
 
# enable query parameters in logs
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
 
# swagger ui customization (springdoc openapi)
springdoc.api-docs.path=/v3/api-docs
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.api-docs.enabled=true
springdoc.swagger-ui.url=/swagger.yml

2.6.1 Properties Explanation

  • Application Configuration:
    • spring.application.name=springboot-hateoas-swagger – Sets the application name as springboot-hateoas-swagger.
    • server.port=10090 – Configures the server to run on port 10090. You’re free to change the port number as per your needs.
  • H2 Database Console:
    • spring.h2.console.enabled=true – Enables the H2 database console for web access.
    • spring.h2.console.path=/h2-console – Sets the H2 console path to /h2-console.
  • Database Configuration:
    • spring.datasource.url=jdbc:h2:mem:testdb – Configures an in-memory H2 database named testdb.
    • spring.datasource.driverClassName=org.h2.Driver – Specifies the H2 database driver.
    • spring.datasource.username=sa – Sets the database username as sa (default for H2).
    • spring.datasource.password= – No password is set for the database.
    • spring.jpa.database-platform=org.hibernate.dialect.H2Dialect – Configures Hibernate to use the H2 dialect.
    • spring.jpa.hibernate.ddl-auto=update – Automatically updates the database schema.
  • Hibernate Settings:
    • spring.jpa.show-sql=true – Enables logging of SQL queries.
    • spring.jpa.properties.hibernate.format_sql=true – Formats SQL queries for better readability in logs.
    • spring.jpa.defer-datasource-initialization=true – Ensures database initialization is deferred until Hibernate is fully set up.
  • Logging Configuration:
    • logging.level.org.hibernate.SQL=DEBUG – Logs SQL queries at the DEBUG level.
    • logging.level.org.hibernate.orm.jdbc.bind=TRACE – Logs SQL query parameters for better debugging.
  • Swagger (SpringDoc OpenAPI) Configuration:
    • springdoc.api-docs.path=/v3/api-docs – Specifies the path for OpenAPI documentation.
    • springdoc.swagger-ui.path=/swagger-ui.html – Sets the Swagger UI access path.
    • springdoc.api-docs.enabled=true – Enables OpenAPI documentation generation.
    • springdoc.swagger-ui.url=/swagger.yml – Configures Swagger UI to load documentation from swagger.yml.

2.7 Main Class

Create a main class that initializes and starts the application. This class will implement CommandLineRunner to populate the table with mock data when the application launches.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.example;
 
import com.example.model.Product;
import com.example.repository.ProductRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
 
import java.util.List;
import java.util.Random;
 
 
@SpringBootApplication
@Slf4j
public class SpringbootHateoasSwaggerApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SpringbootHateoasSwaggerApplication.class, args);
        log.info("Application started successfully.");
    }
 
    @Bean
    CommandLineRunner loadData(ProductRepository repository) {
        return args -> {
 
            if (repository.count() == 0) {
                List<String> productNames = List.of("Laptop", "Smartphone", "Tablet", "Smartwatch", "Headphones");
                Random random = new Random();
                int count = 0;
 
                for (String name : productNames) {
                    double price = 50 + (random.nextDouble() * 950); // Random price between 50 and 1000
                    repository.save(new Product(name, Math.round(price * 100.0) / 100.0)); // Round to 2 decimal places
                    count = count + 1;
                }
 
                System.out.println(count + " random products inserted into the database.");
            } else {
                System.out.println("Skipping...");
            }
        };
    }
}

2.8 Application Run

Launch the Spring Boot application and navigate to http://localhost:10090/swagger-ui.html to access the Swagger UI. Here, users can interact with various APIs, view responses, and explore HATEOAS links for better understanding.

Users can access the Swagger UI, but here is how the API response appears with HATEOAS:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
-- API request: Get all products
curl -X 'GET' \
  -H 'accept: application/json'
 
-- API response
{
  "_embedded": {
    "productList": [
      {
        "id": 1,   
        "name": "Laptop",
        "price": 812.17,
        "_links": {
          "self": {
            "href": "http://localhost:10090/product/1"
          },
          "delete-product": {
            "href": "http://localhost:10090/product/1"
          }
        }
      },
      {
        "id": 2,
        "name": "Smartphone",
        "price": 222.71,
        "_links": {
          "self": {
            "href": "http://localhost:10090/product/2"
          },
          "delete-product": {
            "href": "http://localhost:10090/product/2"
          }
        }
      },
      {
        "id": 3,
        "name": "Tablet",
        "price": 664.89,
        "_links": {
          "self": {
            "href": "http://localhost:10090/product/3"
          },
          "delete-product": {
            "href": "http://localhost:10090/product/3"
          }
        }
      },
      {
        "id": 4,
        "name": "Smartwatch",
        "price": 723.2,
        "_links": {
          "self": {
            "href": "http://localhost:10090/product/4"
          },
          "delete-product": {
            "href": "http://localhost:10090/product/4"
          }
        }
      },
      {
        "id": 5,
        "name": "Headphones",
        "price": 230.93,
        "_links": {
          "self": {
            "href": "http://localhost:10090/product/5"
          },
          "delete-product": {
            "href": "http://localhost:10090/product/5"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:10090/products"
    }
  }
}

This API response follows the HATEOAS (Hypermedia as the Engine of Application State) principle, which enhances RESTful APIs by providing navigable links for resources. The response contains a list of products under the _embedded key, each with details such as id, name, and price. Each product includes an _links section, which provides hypermedia controls, allowing clients to interact with the API dynamically. For example, the self link points to the specific product’s URL (e.g., http://localhost:10090/product/1), while the delete-product link allows deletion of the product. Additionally, at the root level, a _links section provides a self link (http://localhost:10090/products) for retrieving the entire product list. These links enable seamless navigation and interaction without hardcoding endpoint URLs, making the API more discoverable and maintainable.

3. Conclusion

Both Swagger and HATEOAS serve different purposes. Swagger simplifies API documentation and testing, whereas HATEOAS enhances API discoverability. By implementing both, developers can create well-documented and flexible REST APIs.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button