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
:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
For Gradle projects, add to your build.gradle
:
implementation 'io.springfox:springfox-boot-starter:3.0.0'
Configuring Swagger
Create a configuration class to enable and configure Swagger:
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:
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:
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:
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
:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.15</version>
</dependency>
For Gradle projects:
implementation 'org.springdoc:springdoc-openapi-ui:1.6.15'
Basic Configuration
Create a configuration class:
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:
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:
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
:
<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:
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:
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:
= 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
- Be Consistent: Maintain a consistent style and terminology throughout your documentation
- Provide Examples: Include request/response examples for all endpoints
- Document Error Cases: Explain what happens when things go wrong
- Keep It Updated: Ensure documentation stays in sync with your API implementation
- Consider Your Audience: Write documentation with your target developers in mind
- Include Authentication Details: Explain how to authenticate with your API
- Version Your API and Documentation: Make clear which version of the API the documentation refers to
Comparing Documentation Approaches
Approach | Pros | Cons |
---|---|---|
Swagger/SpringFox | Easy to set up, interactive UI | Annotations can clutter code |
OpenAPI/SpringDoc | Industry standard, better tooling | Similar annotation overhead |
Spring REST Docs | Documentation always in sync with code | Steeper learning curve, requires tests |
Real-world Example: E-Commerce API Documentation
For a complete e-commerce application, your API documentation would cover endpoints for:
- Product Catalog: Browsing, searching and filtering products
- Shopping Cart: Adding, removing and updating items
- User Management: Registration, authentication, profile management
- Order Processing: Creating orders, payment processing, order history
- 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:
- Swagger/SpringFox: A code-first approach that uses annotations to generate interactive documentation
- OpenAPI/SpringDoc: The modern evolution of Swagger that follows the OpenAPI 3.0 specification
- 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
- Official SpringFox Documentation
- SpringDoc OpenAPI Documentation
- Spring REST Docs Reference Guide
- The OpenAPI Specification
Practice Exercises
- Add Swagger documentation to an existing Spring REST API project
- Convert a Swagger-documented API to use OpenAPI 3.0
- Create a simple REST API with CRUD operations and document it using Spring REST Docs
- Document API authentication methods using your preferred documentation approach
- 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! :)