APIs · Lesson 11
OpenAPI/Swagger Documentation
What is OpenAPI/Swagger?
OpenAPI (formerly Swagger) is a standard for describing REST APIs in a machine-readable format (YAML or JSON). It defines endpoints, parameters, request/response formats, and authentication. Swagger is a UI tool that visualizes OpenAPI specifications, letting you explore and test API endpoints interactively. OpenAPI has become the industry standard for API documentation — it enables automatic client code generation and ensures documentation stays synchronized with actual code.
OpenAPI Specification Structure
An OpenAPI specification defines an API's contract. Here's a simple example:
# openapi.yaml - OpenAPI specification
openapi: 3.0.0
info:
title: User Management API
version: 1.0.0
description: API for managing users
contact:
name: Support
email: support@example.com
servers:
- url: https://api.example.com/v1
description: Production server
- url: https://staging-api.example.com/v1
description: Staging server
paths:
/users:
get:
summary: Get all users
operationId: getUsers
tags:
- Users
parameters:
- name: page
in: query
required: false
schema:
type: integer
default: 1
- name: size
in: query
required: false
schema:
type: integer
default: 10
responses:
'200':
description: List of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
'400':
description: Bad request
post:
summary: Create a new user
operationId: createUser
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserInput'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid input
/users/{id}:
get:
summary: Get user by ID
operationId: getUserById
tags:
- Users
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: User details
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
components:
schemas:
User:
type: object
required:
- id
- name
- email
properties:
id:
type: integer
example: 123
name:
type: string
example: Alice
email:
type: string
format: email
example: alice@example.com
createdAt:
type: string
format: date
example: 2023-01-15
UserInput:
type: object
required:
- name
- email
- password
properties:
name:
type: string
example: Alice
email:
type: string
format: email
example: alice@example.com
password:
type: string
format: password
example: secret123
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
Springfox: Generating OpenAPI from Spring Boot Code
Springfox is a Spring Boot integration that automatically generates OpenAPI documentation from your @RestController annotations. No need to write YAML manually — Springfox scans your code and creates documentation. With Swagger UI dependency, you get an interactive endpoint explorer at /swagger-ui.html.
// Maven/Gradle dependency
/*
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.12</version>
</dependency>
*/
// Spring Boot automatically generates OpenAPI at /v3/api-docs
// Swagger UI available at /swagger-ui.html
// Annotate your API endpoints for documentation
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.tags.Tag;
@RestController
@RequestMapping("/api/users")
@Tag(name = "Users", description = "User management endpoints")
public class UserController {
@Operation(summary = "Get all users", description = "Retrieve a paginated list of all users")
@ApiResponse(responseCode = "200", description = "Users retrieved successfully")
@GetMapping
public List<User> getUsers(
@Parameter(description = "Page number (0-indexed)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "Page size") @RequestParam(defaultValue = "10") int size) {
// Implementation
return new ArrayList<>();
}
@Operation(summary = "Get user by ID", description = "Retrieve a specific user by their ID")
@ApiResponse(responseCode = "200", description = "User found")
@ApiResponse(responseCode = "404", description = "User not found")
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(
@Parameter(description = "User ID", required = true) @PathVariable int id) {
// Implementation
return ResponseEntity.ok(new User());
}
@Operation(summary = "Create user", description = "Create a new user account")
@ApiResponse(responseCode = "201", description = "User created successfully")
@ApiResponse(responseCode = "400", description = "Invalid user data")
@PostMapping
public ResponseEntity<User> createUser(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
required = true,
content = @Content(schema = @Schema(implementation = UserInput.class))
)
@RequestBody UserInput userInput) {
// Implementation
return ResponseEntity.status(201).body(new User());
}
@Operation(summary = "Update user", description = "Update an existing user")
@ApiResponse(responseCode = "200", description = "User updated")
@ApiResponse(responseCode = "404", description = "User not found")
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@Parameter(description = "User ID", required = true) @PathVariable int id,
@RequestBody UserInput userInput) {
// Implementation
return ResponseEntity.ok(new User());
}
@Operation(summary = "Delete user", description = "Delete a user by ID")
@ApiResponse(responseCode = "204", description = "User deleted")
@ApiResponse(responseCode = "404", description = "User not found")
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(
@Parameter(description = "User ID", required = true) @PathVariable int id) {
// Implementation
return ResponseEntity.noContent().build();
}
}
// Data classes with schema documentation
@Schema(description = "User data")
class User {
@Schema(description = "User unique identifier", example = "123")
private int id;
@Schema(description = "User full name", example = "Alice Johnson")
private String name;
@Schema(description = "User email address", example = "alice@example.com")
private String email;
@Schema(description = "Account creation date", example = "2023-01-15")
private String createdAt;
// Getters, setters...
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getCreatedAt() { return createdAt; }
}
@Schema(description = "User input for registration/update")
class UserInput {
@Schema(description = "User full name", example = "Alice Johnson", required = true)
private String name;
@Schema(description = "User email address", example = "alice@example.com", required = true)
private String email;
@Schema(description = "User password", example = "SecurePassword123", required = true)
private String password;
// Getters, setters...
public String getName() { return name; }
public String getEmail() { return email; }
public String getPassword() { return password; }
}
API Versioning Strategies
As APIs evolve, you must maintain backward compatibility. Common strategies: URL versioning (/v1/, /v2/), header versioning (Accept header specifies version), query parameter versioning (?version=2).
import org.springframework.web.bind.annotation.*;
// URL versioning (most common)
@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
@GetMapping("/{id}")
public UserV1 getUserById(@PathVariable int id) {
return new UserV1(1, "Alice");
}
}
@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
@GetMapping("/{id}")
public UserV2 getUserById(@PathVariable int id) {
UserV2 user = new UserV2(1, "Alice", "alice@example.com");
user.setPhoneNumber("+1234567890"); // New field in v2
return user;
}
}
// V1 response
class UserV1 {
private int id;
private String name;
UserV1(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() { return id; }
public String getName() { return name; }
}
// V2 response (backward compatible, adds field)
class UserV2 extends UserV1 {
private String email;
private String phoneNumber;
UserV2(int id, String name, String email) {
super(id, name);
this.email = email;
}
public String getEmail() { return email; }
public void setPhoneNumber(String phone) { this.phoneNumber = phone; }
public String getPhoneNumber() { return phoneNumber; }
}
// Header versioning (alternative)
@RestController
@RequestMapping("/api/users")
public class UserControllerHeaderVersion {
@GetMapping("/{id}", headers = "Accept-Version=1")
public UserV1 getUserV1(@PathVariable int id) {
return new UserV1(1, "Alice");
}
@GetMapping("/{id}", headers = "Accept-Version=2")
public UserV2 getUserV2(@PathVariable int id) {
return new UserV2(1, "Alice", "alice@example.com");
}
}
| API Documentation Best Practice | Purpose | Example |
|---|---|---|
| Use OpenAPI/Swagger | Standard format for API contracts | openapi.yaml specification |
| Annotate endpoints | Auto-generate documentation | @Operation, @ApiResponse |
| Describe parameters | Clarify what inputs are needed | @Parameter annotations |
| Provide schema examples | Show expected request/response format | @Schema with example fields |
| Document error responses | Explain what can go wrong | 404, 400, 500 responses |
| Version APIs | Support gradual updates | /api/v1/, /api/v2/ |
| Use Swagger UI | Interactive endpoint testing | Built-in with Springfox |
Common Mistake: Not documenting APIs or letting documentation drift from code. Without documentation, developers can't use your API correctly. Swagger/OpenAPI solves this by generating docs automatically from code annotations. Also, breaking changes in APIs without versioning. Always version APIs; never remove fields without supporting older versions.
Best Practices for API Documentation
- Use OpenAPI/Swagger for standardized documentation
- Annotate all endpoints with @Operation and @ApiResponse
- Include examples in schema definitions
- Document error responses (400, 401, 403, 404, 500)
- Version your APIs (URL versioning is clearest)
- Maintain backward compatibility when possible
- Use Swagger UI for interactive exploration
- Keep documentation updated — automated generation (via Springfox) ensures sync
- Document authentication (API keys, JWT tokens)
Real-World Example: REST API with Full Documentation
A REST API for e-commerce with Springfox generates OpenAPI automatically. Every @RestController method is annotated with @Operation, @ApiResponse, and @Parameter. The OpenAPI spec is available at /v3/api-docs; Swagger UI interactive explorer is at /swagger-ui.html. Developers visit Swagger UI, see all endpoints, click "Try it out", enter parameters, and test responses instantly without writing code. The API is versioned (/api/v1/products, /api/v2/products) to support legacy clients. When v2 adds new fields, v1 clients still work unchanged. Documentation is always in sync because it's generated from code annotations.
Quiz Check