Java Academy

Master Java Development

Menu

Core Java

Servlets

JDBC

Microservices

Build Tools

APIs

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 PracticePurposeExample
Use OpenAPI/SwaggerStandard format for API contractsopenapi.yaml specification
Annotate endpointsAuto-generate documentation@Operation, @ApiResponse
Describe parametersClarify what inputs are needed@Parameter annotations
Provide schema examplesShow expected request/response format@Schema with example fields
Document error responsesExplain what can go wrong404, 400, 500 responses
Version APIsSupport gradual updates/api/v1/, /api/v2/
Use Swagger UIInteractive endpoint testingBuilt-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

Quick Knowledge Test

Loading quiz questions...