SpringDoc annotation reference

This reference provides comprehensive documentation of SpringDoc annotations used to generate OpenAPI documentation from Java code. Use this guide to understand how annotations create documentation and collaborate effectively with developers.


Overview: SpringDoc annotations

SpringDoc uses annotations to automatically generate OpenAPI 3.0 specifications from Java code. These annotations are added directly to controller classes, methods, and parameters to create comprehensive API documentation.

Annotation categories

Category Purpose Key Annotations
Endpoint Documentation Document API operations @Operation, @ApiResponses
Parameter Documentation Document request parameters @Parameter, @RequestBody
Response Documentation Document API responses @ApiResponse, @Content, @Schema
Organization Group and organize endpoints @Tag, @Hidden
Examples Provide request/response examples @ExampleObject, @Example
Schema Definition Define data models @Schema, @ArraySchema

Core endpoint annotations

@Operation

Purpose: Documents the main information for an API endpoint.

Location: Method level (on controller methods)

Example:

@Operation(
    summary = "Create a new customer",
    description = """
        Creates a new customer with complete profile information including nested address data.
        This endpoint demonstrates:
        - Nested object handling (Address within Customer)
        - Enum validation (CustomerType)
        - Input validation and error handling
        - Database persistence with JPA
        """,
    tags = {"Single customer CRUD operations"},
    operationId = "createCustomer"
)
@PostMapping("/customer")
public String createCustomer(@RequestBody Customer customer) {
    // Implementation
}

Generated OpenAPI:

paths:
  /customer:
    post:
      summary: Create a new customer
      description: |
        Creates a new customer with complete profile information including nested address data.
        This endpoint demonstrates:
        - Nested object handling (Address within Customer)
        - Enum validation (CustomerType)
        - Input validation and error handling
        - Database persistence with JPA
      tags:
        - Single customer CRUD operations
      operationId: createCustomer

Key Properties:

  • summary - Brief description (appears in endpoint lists)
  • description - Detailed explanation (supports markdown)
  • tags - Groups endpoints in Swagger UI
  • operationId - Unique identifier for code generation
  • deprecated - Mark endpoint as deprecated
  • hidden - Hide endpoint from documentation

@ApiResponses and @ApiResponse

Purpose: Documents all possible HTTP responses for an endpoint.

Location: Method level (on controller methods)

Example:

@ApiResponses({
    @ApiResponse(
        responseCode = "200",
        description = "Customer created successfully",
        content = @Content(
            mediaType = "text/plain",
            examples = @ExampleObject(value = "Customer added successfully")
        )
    ),
    @ApiResponse(
        responseCode = "400",
        description = "Invalid customer data provided",
        content = @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = ErrorResponse.class),
            examples = @ExampleObject(value = """
                {
                  "message": "Invalid customer data: ID format must be CUSTXXX",
                  "timestamp": "2026-01-25T10:30:00.000Z",
                  "status": 400,
                  "error": "Bad Request"
                }
                """)
        )
    ),
    @ApiResponse(
        responseCode = "409",
        description = "Customer with this ID already exists"
    )
})

Generated OpenAPI:

responses:
  '200':
    description: Customer created successfully
    content:
      text/plain:
        examples:
          default:
            value: "Customer added successfully"
  '400':
    description: Invalid customer data provided
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/ErrorResponse'
        examples:
          default:
            value:
              message: "Invalid customer data: ID format must be CUSTXXX"
              timestamp: "2026-01-25T10:30:00.000Z"
              status: 400
              error: "Bad Request"
  '409':
    description: Customer with this ID already exists

Key Properties:

  • responseCode - HTTP status code (as string)
  • description - Response description
  • content - Response body specification
  • headers - Response headers documentation

Parameter documentation annotations

@Parameter

Purpose: Documents request parameters (path, query, header, cookie).

Location: Parameter level (on method parameters)

Path Parameter Example:

@GetMapping("/customer/{customerID}")
public Customer getCustomer(
    @Parameter(
        description = "Unique customer identifier in format CUSTXXX",
        example = "CUST001",
        required = true
    )
    @PathVariable String customerID
) {
    // Implementation
}

Query Parameter Example:

@GetMapping("/customers")
public PaginatedResponse<Customer> getCustomers(
    @Parameter(
        description = "Page number for pagination (starts at 0)",
        example = "0",
        schema = @Schema(type = "integer", minimum = "0")
    )
    @RequestParam(defaultValue = "0") int page,

    @Parameter(
        description = "Number of items per page",
        example = "10",
        schema = @Schema(type = "integer", minimum = "1", maximum = "100")
    )
    @RequestParam(defaultValue = "10") int size
) {
    // Implementation
}

Generated OpenAPI:

/customer/{customerID}:
  get:
    parameters:
      - name: customerID
        in: path
        description: Unique customer identifier in format CUSTXXX
        required: true
        example: "CUST001"
        schema:
          type: string

/customers:
  get:
    parameters:
      - name: page
        in: query
        description: Page number for pagination (starts at 0)
        required: false
        example: 0
        schema:
          type: integer
          minimum: 0
      - name: size
        in: query
        description: Number of items per page
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          maximum: 100

Request body documentation

Purpose: Documents request body content and structure.

Example:

@PostMapping("/customer")
public String createCustomer(
    @Parameter(
        description = "Customer object with all required fields including nested address",
        required = true,
        content = @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = Customer.class),
            examples = @ExampleObject(
                name = "Complete Customer Example",
                summary = "Customer with all fields",
                description = "Example showing a complete customer record with all optional fields included",
                value = """
                    {
                      "id": "CUST001",
                      "name": "Alice Johnson",
                      "address": {
                        "street": "123 Main St",
                        "city": "New York",
                        "state": "NY",
                        "zipCode": "10001",
                        "country": "USA"
                      },
                      "phone": "555-0123",
                      "type": "VIP"
                    }
                    """
            )
        )
    )
    @RequestBody Customer customer
) {
    // Implementation
}

Schema and model annotations

@Schema (on Classes)

Purpose: Documents data model structure and validation rules.

Location: Class level and field level

Class Level Example:

@Schema(
    description = "Complete customer information including contact details and address",
    example = """
        {
          "id": "CUST001",
          "name": "Alice Johnson",
          "phone": "555-0123",
          "type": "VIP",
          "address": {
            "street": "123 Main St",
            "city": "New York",
            "state": "NY",
            "zipCode": "10001",
            "country": "USA"
          }
        }
        """
)
@Entity
public class Customer {
    // Fields and methods
}

Field Level Examples:

@Entity
public class Customer {
    @Schema(
        description = "Unique customer identifier in format CUSTXXX where XXX is 3 digits",
        example = "CUST001",
        pattern = "^CUST[0-9]{3}$",
        required = true
    )
    @Id
    private String id;

    @Schema(
        description = "Customer full name",
        example = "Alice Johnson",
        minLength = 1,
        maxLength = 100,
        required = true
    )
    private String name;

    @Schema(
        description = "Customer phone number (optional)",
        example = "555-0123",
        pattern = "^[0-9\\-\\+\\(\\)\\s]+$"
    )
    private String phone;

    @Schema(
        description = "Customer classification level",
        allowableValues = {"INDIVIDUAL", "BUSINESS", "VIP", "PREMIUM"},
        example = "VIP"
    )
    @Enumerated(EnumType.STRING)
    private CustomerType type;

    @Schema(description = "Customer address information")
    @Embedded
    private Address address;
}

Generated OpenAPI:

components:
  schemas:
    Customer:
      type: object
      description: Complete customer information including contact details and address
      required:
        - id
        - name
      properties:
        id:
          type: string
          description: Unique customer identifier in format CUSTXXX where XXX is 3 digits
          example: "CUST001"
          pattern: "^CUST[0-9]{3}$"
        name:
          type: string
          description: Customer full name
          example: "Alice Johnson"
          minLength: 1
          maxLength: 100
        phone:
          type: string
          description: Customer phone number (optional)
          example: "555-0123"
          pattern: "^[0-9\\-\\+\\(\\)\\s]+$"
        type:
          type: string
          description: Customer classification level
          enum: [INDIVIDUAL, BUSINESS, VIP, PREMIUM]
          example: "VIP"
        address:
          $ref: '#/components/schemas/Address'

@Schema Properties Reference

Property Purpose Example
description Field explanation "Customer full name"
example Example value "Alice Johnson"
required Mark as required field required = true
pattern Regex validation "^CUST[0-9]{3}$"
minLength / maxLength String length limits minLength = 1, maxLength = 100
minimum / maximum Numeric limits minimum = "0", maximum = "150"
allowableValues Enum values {"INDIVIDUAL", "BUSINESS"}
format Data format "email", "date-time", "uuid"
hidden Hide from documentation hidden = true

Organization and grouping annotations

@Tag

Purpose: Groups related endpoints together in Swagger UI.

Class Level Example:

@Tag(
    name = "Customer Management",
    description = "Operations for managing customer information including CRUD operations, search, and batch processing"
)
@RestController
public class CustomerAPIService {
    // Controller methods
}

Method Level Example:

@Operation(
    summary = "Search customers by criteria",
    tags = {"Customer Search", "Advanced Operations"}
)
@GetMapping("/customers/search")
public PaginatedResponse<CustomerSummary> searchCustomers(/* parameters */) {
    // Implementation
}

@Hidden

Purpose: Excludes endpoints or parameters from generated documentation.

Examples:

@Hidden  // This endpoint won't appear in documentation
@GetMapping("/internal/debug")
public String debugEndpoint() {
    // Internal endpoint
}

@GetMapping("/customers")
public PaginatedResponse<Customer> getCustomers(
    @Parameter(hidden = true)  // This parameter won't be documented
    @RequestParam(required = false) String internalFlag
) {
    // Implementation
}

Example and content annotations

@ExampleObject

Purpose: Provides detailed examples for requests and responses.

Single Example:

@ApiResponse(
    responseCode = "200",
    description = "Customer retrieved successfully",
    content = @Content(
        mediaType = "application/json",
        schema = @Schema(implementation = Customer.class),
        examples = @ExampleObject(
            name = "VIP Customer Example",
            summary = "Example of a VIP customer response",
            description = "Shows a complete VIP customer with all fields populated",
            value = """
                {
                  "name": "Alice Johnson",
                  "id": "CUST001",
                  "address": {
                    "street": "123 Main St",
                    "city": "New York",
                    "state": "NY",
                    "zipCode": "10001",
                    "country": "USA",
                    "formattedAddress": "123 Main St, New York, NY 10001, USA"
                  },
                  "phone": "555-0123",
                  "type": "VIP",
                  "city": "New York",
                  "businessCustomer": false,
                  "highValueCustomer": true
                }
                """
        )
    )
)

Multiple Examples:

@ApiResponse(
    responseCode = "400",
    description = "Validation errors",
    content = @Content(
        mediaType = "application/json",
        schema = @Schema(implementation = ErrorResponse.class),
        examples = {
            @ExampleObject(
                name = "Missing Required Field",
                summary = "Required field validation error",
                value = """
                    {
                      "message": "Customer name is required",
                      "timestamp": "2026-01-25T10:30:00.000Z",
                      "status": 400,
                      "error": "Bad Request"
                    }
                    """
            ),
            @ExampleObject(
                name = "Invalid Format",
                summary = "Format validation error",
                value = """
                    {
                      "message": "Customer ID format must be CUSTXXX",
                      "timestamp": "2026-01-25T10:30:00.000Z",
                      "status": 400,
                      "error": "Bad Request"
                    }
                    """
            )
        }
    )
)

Configuration and global settings

OpenAPI Configuration Class

Purpose: Global API metadata and configuration.

Example:

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("Customer Management APIs")
                .version("1.0.0")
                .description("REST API with H2 database integration")
                .contact(new Contact()
                    .name("API Support")
                    .email("support@example.com"))
                .license(new License()
                    .name("MIT License")
                    .url("https://opensource.org/licenses/MIT")))
            .servers(List.of(
                new Server()
                    .url("http://localhost:8080")
                    .description("Development Server"),
                new Server()
                    .url("https://api.example.com")
                    .description("Production Server")
            ))
            .addSecurityItem(new SecurityRequirement().addList("basicAuth"))
            .components(new Components()
                .addSecuritySchemes("basicAuth",
                    new SecurityScheme()
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("basic")
                        .description("HTTP Basic Authentication")));
    }
}

Best practices for technical writers

1. Annotation Quality Guidelines

Effective Summaries:

// Good - Clear and concise
@Operation(summary = "Create a new customer")

// Bad - Too verbose or vague
@Operation(summary = "This endpoint creates a new customer record in the database")
@Operation(summary = "Create something")

Helpful Descriptions:

// Good - Provides context and business rules
@Operation(
    summary = "Delete customer by ID",
    description = """
        Permanently removes a customer from the system. This operation cannot be undone.

        Business Rules:
        - Customer must exist
        - Customer cannot have active orders
        - Requires ADMIN role permissions

        The customer's order history will be preserved but anonymized.
        """
)

// Bad - Just restates the summary
@Operation(
    summary = "Delete customer",
    description = "Deletes a customer"
)

Realistic Examples:

// Good - Realistic, helpful data
@Parameter(
    description = "Customer ID",
    example = "CUST001"  // Follows actual format
)

// Bad - Generic or inconsistent data
@Parameter(
    description = "Customer ID",
    example = "123"  // Doesn't match actual format
)

2. Collaboration with Developers

Enhancement Requests: When requesting annotation improvements, provide specific text:

// Current annotation
@Operation(summary = "Update customer")

// Suggested improvement
@Operation(
    summary = "Update customer information",
    description = """
        Updates an existing customer's complete information. All fields in the request
        will replace the current values - use PATCH for partial updates.

        Business Rules:
        - Customer ID cannot be changed
        - Phone number format is validated
        - Address information is required
        """
)

Testing Annotation Changes:

# After developers update annotations:
1. Restart the application
./mvnw spring-boot:run

2. Check generated documentation
curl http://localhost:8080/v3/api-docs.yaml > updated-spec.yaml

3. Compare with previous version
diff previous-spec.yaml updated-spec.yaml

4. Test examples in Swagger UI
open http://localhost:8080/swagger-ui/index.html

3. Documentation Maintenance

Monitor for Missing Annotations:

  • New endpoints without @Operation
  • Parameters without descriptions
  • Missing response documentation
  • Outdated examples

Version Control Integration:

# Track annotation changes alongside code
git log --oneline -- "*.java" | grep -i "annotation\|documentation\|openapi"

# Review documentation impact of code changes
git diff HEAD~1 -- src/main/java/*/controller/*.java

Common issues and solutions

Issue 1: Examples Don’t Match Reality

Problem: Annotation examples don’t work when tested.

Solution:

// Test examples against running API
curl -X POST http://localhost:8080/customer \
  -H "Content-Type: application/json" \
  -d '/* paste example from annotation */'

// Update annotation with working example
@Parameter(
    example = "WORKING_EXAMPLE_THAT_WAS_TESTED"
)

Issue 2: Missing Business Context

Problem: Technical accuracy but poor user experience.

Solution:

// Add business context to descriptions
@Operation(
    description = """
        Technical: Updates customer record in database
        Business: Use this when customer information changes (address, phone, etc.)
        Workflow: Customer calls → Update info → Send confirmation email
        """
)

Issue 3: Inconsistent Documentation Style

Problem: Different developers use different annotation styles.

Solution: Create annotation guidelines document:

// Standard template for all endpoints
@Operation(
    summary = "[Verb] [noun]",  // e.g., "Create customer", "Delete order"
    description = """
        [What it does - one sentence]

        Business Rules:
        - Rule 1
        - Rule 2

        [Additional context if needed]
        """,
    tags = {"[Logical grouping]"}
)

This reference provides comprehensive coverage of SpringDoc annotations for generating OpenAPI documentation from Java code. Use it to understand current annotations, request improvements, and ensure documentation quality in code-first development workflows.