Skip to main content

Spring REST Documentation

Introduction

Documentation is a critical component of any REST API. Without proper documentation, even the most well-designed API can be difficult to use. In the Spring ecosystem, we have several options for creating clear, interactive API documentation for our REST services.

In this guide, we'll explore different approaches to documenting Spring REST APIs, focusing on:

  • Why API documentation matters
  • Using Swagger and SpringFox
  • Implementing OpenAPI 3.0 with SpringDoc
  • Creating documentation with Spring REST Docs
  • Best practices for API documentation

By the end of this guide, you'll have the knowledge to create comprehensive documentation for your Spring REST APIs that developers will love to use.

Why Document Your APIs?

Before diving into the implementation details, let's understand why documentation is so important:

  • Improved Developer Experience: Good documentation makes your API easier to understand and use
  • Reduced Support Burden: Well-documented APIs result in fewer questions and support requests
  • Better Adoption: Developers are more likely to adopt well-documented APIs
  • Testing and Validation: Documentation tools often provide ways to validate your API implementation

Swagger and SpringFox

Swagger (now part of the OpenAPI Initiative) is one of the most popular API documentation frameworks. SpringFox is a library that integrates Swagger into Spring applications.

Adding SpringFox to Your Project

To use SpringFox, add the following dependencies to your pom.xml:

xml
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

For Gradle projects, add to your build.gradle:

groovy
implementation 'io.springfox:springfox-boot-starter:3.0.0'

Configuring Swagger

Create a configuration class to enable and configure Swagger:

java
package com.example.restdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Collections;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.restdemo.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}

private ApiInfo apiInfo() {
return new ApiInfo(
"My REST API",
"API Documentation for Spring REST Demo",
"1.0",
"Terms of service",
new Contact("John Doe", "www.example.com", "[email protected]"),
"License of API", "API license URL", Collections.emptyList());
}
}

Documenting Controllers and Models

Use Swagger annotations to document your controllers and models:

java
package com.example.restdemo.controller;

import com.example.restdemo.model.Product;
import com.example.restdemo.service.ProductService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
@Api(value = "Product Management", description = "Operations pertaining to products")
public class ProductController {

@Autowired
private ProductService productService;

@ApiOperation(value = "View a list of available products", response = List.class)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully retrieved list"),
@ApiResponse(code = 401, message = "You are not authorized to view the resource"),
@ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found")
})
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(productService.getAllProducts());
}

@ApiOperation(value = "Get a product by Id")
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(
@ApiParam(value = "Product id from which product object will be retrieved", required = true)
@PathVariable Long id) {
return ResponseEntity.ok(productService.getProductById(id));
}

// Other controller methods
}

And for your model classes:

java
package com.example.restdemo.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel(description = "Details about the Product")
public class Product {

@ApiModelProperty(notes = "The unique id of the product")
private Long id;

@ApiModelProperty(notes = "The product name")
private String name;

@ApiModelProperty(notes = "The product description")
private String description;

@ApiModelProperty(notes = "The product price")
private double price;

// Getters, setters, constructors, etc.
}

Accessing the Swagger UI

With SpringFox configured, you can access the Swagger UI at:

http://localhost:8080/swagger-ui/

This gives you an interactive interface to explore and test your API:

Swagger UI Example

OpenAPI 3.0 with SpringDoc

The OpenAPI Specification (formerly Swagger Specification) is now the industry standard for REST API documentation. SpringDoc provides integration between Spring Boot and OpenAPI 3.

Adding SpringDoc to Your Project

Add the following dependency to your pom.xml:

xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.15</version>
</dependency>

For Gradle projects:

groovy
implementation 'org.springdoc:springdoc-openapi-ui:1.6.15'

Basic Configuration

Create a configuration class:

java
package com.example.restdemo.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {

@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Spring REST Demo API")
.version("1.0")
.description("Documentation for Spring REST Demo API")
.termsOfService("http://swagger.io/terms/")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}
}

Documenting with OpenAPI Annotations

OpenAPI uses a different set of annotations:

java
package com.example.restdemo.controller;

import com.example.restdemo.model.Product;
import com.example.restdemo.service.ProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
@Tag(name = "Product", description = "Product management APIs")
public class ProductController {

@Autowired
private ProductService productService;

@Operation(summary = "Get a list of all products", description = "Returns a list of available products")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved list",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Product.class))),
@ApiResponse(responseCode = "401", description = "You are not authorized to view the resource"),
@ApiResponse(responseCode = "404", description = "The resource you were trying to reach is not found")
})
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(productService.getAllProducts());
}

@Operation(summary = "Get a product by id")
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(
@Parameter(description = "Id of the product to be obtained. Cannot be empty.", required = true)
@PathVariable Long id) {
return ResponseEntity.ok(productService.getProductById(id));
}

// Other controller methods
}

And for model classes:

java
package com.example.restdemo.model;

import io.swagger.v3.oas.annotations.media.Schema;

public class Product {

@Schema(description = "Unique identifier of the product", example = "1")
private Long id;

@Schema(description = "Name of the product", example = "Smartphone")
private String name;

@Schema(description = "Detailed description of the product", example = "Latest model with high-end features")
private String description;

@Schema(description = "Price of the product", example = "999.99")
private double price;

// Getters, setters, constructors, etc.
}

Accessing the OpenAPI UI

Access the OpenAPI UI at:

http://localhost:8080/swagger-ui.html

Spring REST Docs

Spring REST Docs takes a different approach by generating documentation from test cases. This ensures that documentation always stays in sync with the actual API behavior.

Adding Spring REST Docs to Your Project

Add the following to your pom.xml:

xml
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>

<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<sourceDocumentName>index.adoc</sourceDocumentName>
<backend>html</backend>
<attributes>
<snippets>${project.build.directory}/generated-snippets</snippets>
</attributes>
</configuration>
</execution>
</executions>
</plugin>

For Gradle:

groovy
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

plugins {
id "org.asciidoctor.jvm.convert" version "3.3.2"
}

ext {
snippetsDir = file('build/generated-snippets')
}

test {
outputs.dir snippetsDir
}

asciidoctor {
inputs.dir snippetsDir
dependsOn test
}

Creating Documentation Tests

Write test cases for your controllers:

java
package com.example.restdemo.controller;

import com.example.restdemo.model.Product;
import com.example.restdemo.service.ProductService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Arrays;
import java.util.List;

import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(ProductController.class)
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
public class ProductControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private ProductService productService;

@Test
public void getAllProducts() throws Exception {
// Create test data
List<Product> products = Arrays.asList(
new Product(1L, "Smartphone", "Latest model", 999.99),
new Product(2L, "Laptop", "High performance", 1299.99)
);

when(productService.getAllProducts()).thenReturn(products);

mockMvc.perform(get("/api/products")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].id").value(1))
.andExpect(jsonPath("$[0].name").value("Smartphone"))
.andDo(document("get-all-products",
responseFields(
fieldWithPath("[].id").description("The unique id of the product"),
fieldWithPath("[].name").description("The name of the product"),
fieldWithPath("[].description").description("The product description"),
fieldWithPath("[].price").description("The product price")
)
));
}

@Test
public void getProductById() throws Exception {
Product product = new Product(1L, "Smartphone", "Latest model", 999.99);

when(productService.getProductById(1L)).thenReturn(product);

mockMvc.perform(get("/api/products/{id}", 1L)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Smartphone"))
.andDo(document("get-product-by-id",
pathParameters(
parameterWithName("id").description("The id of the product to retrieve")
),
responseFields(
fieldWithPath("id").description("The unique id of the product"),
fieldWithPath("name").description("The name of the product"),
fieldWithPath("description").description("The product description"),
fieldWithPath("price").description("The product price")
)
));
}
}

Creating AsciiDoc Documentation

Create an src/docs/asciidoc/index.adoc file:

asciidoc
= REST API Documentation
Doc Writer <[email protected]>
v1.0, 2023-05-01
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3
:sectlinks:

== Introduction

This is an example of Spring REST Docs generated documentation.

== Product API

=== Get all products

.request
include::{snippets}/get-all-products/http-request.adoc[]

.response
include::{snippets}/get-all-products/http-response.adoc[]

.response fields
include::{snippets}/get-all-products/response-fields.adoc[]

=== Get product by ID

.request
include::{snippets}/get-product-by-id/http-request.adoc[]

.path parameters
include::{snippets}/get-product-by-id/path-parameters.adoc[]

.response
include::{snippets}/get-product-by-id/http-response.adoc[]

.response fields
include::{snippets}/get-product-by-id/response-fields.adoc[]

After running tests, the documentation will be generated and can be found in target/generated-docs/index.html (Maven) or build/docs/asciidoc/index.html (Gradle).

Best Practices for API Documentation

  1. Be Consistent: Maintain a consistent style and terminology throughout your documentation
  2. Provide Examples: Include request/response examples for all endpoints
  3. Document Error Cases: Explain what happens when things go wrong
  4. Keep It Updated: Ensure documentation stays in sync with your API implementation
  5. Consider Your Audience: Write documentation with your target developers in mind
  6. Include Authentication Details: Explain how to authenticate with your API
  7. Version Your API and Documentation: Make clear which version of the API the documentation refers to

Comparing Documentation Approaches

ApproachProsCons
Swagger/SpringFoxEasy to set up, interactive UIAnnotations can clutter code
OpenAPI/SpringDocIndustry standard, better toolingSimilar annotation overhead
Spring REST DocsDocumentation always in sync with codeSteeper learning curve, requires tests

Real-world Example: E-Commerce API Documentation

For a complete e-commerce application, your API documentation would cover endpoints for:

  1. Product Catalog: Browsing, searching and filtering products
  2. Shopping Cart: Adding, removing and updating items
  3. User Management: Registration, authentication, profile management
  4. Order Processing: Creating orders, payment processing, order history
  5. Shipping and Delivery: Tracking, delivery options

Each endpoint would include:

  • URL and HTTP method
  • Request parameters and body schema
  • Response status codes and body schema
  • Authentication requirements
  • Rate limiting information
  • Example requests and responses

Summary

In this guide, we explored three main approaches to documenting Spring REST APIs:

  1. Swagger/SpringFox: A code-first approach that uses annotations to generate interactive documentation
  2. OpenAPI/SpringDoc: The modern evolution of Swagger that follows the OpenAPI 3.0 specification
  3. Spring REST Docs: A test-driven approach that keeps documentation and code in sync

Each approach has its strengths and is suitable for different situations. For most beginners, starting with Swagger or OpenAPI is recommended due to the ease of setup and the interactive documentation it provides.

Additional Resources

Practice Exercises

  1. Add Swagger documentation to an existing Spring REST API project
  2. Convert a Swagger-documented API to use OpenAPI 3.0
  3. Create a simple REST API with CRUD operations and document it using Spring REST Docs
  4. Document API authentication methods using your preferred documentation approach
  5. Create a complete product catalog API with comprehensive documentation

By following this guide and working through the exercises, you'll be able to create clear, comprehensive, and developer-friendly documentation for your Spring REST APIs.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)