Springdoc OpenAPI (Swagger)

SkillDocs & knowledge

Springdoc OpenAPI for API documentation in Spring Boot. Covers Swagger UI configuration, annotations, schema customization, and security documentation. Based on production patterns from castellino and gestionale-presenze projects.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Springdoc OpenAPI (Swagger) skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/api-design/springdoc-openapi/SKILL.md and read by ahel’s review.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: springdoc-openapi for comprehensive documentation.

Maven Configuration

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.6.0</version>
</dependency>

Application Configuration

springdoc:
  api-docs:
    path: /v3/api-docs
    enabled: true
  swagger-ui:
    path: /swagger-ui.html
    enabled: true
    operationsSorter: method
    tagsSorter: alpha
    displayRequestDuration: true
    filter: true
  packages-to-scan: com.example.controller
  paths-to-match: /api/**

OpenAPI Configuration

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("My API")
                .version("1.0.0")
                .description("REST API Documentation")
                .contact(new Contact()
                    .name("API Support")
                    .email("support@example.com"))
                .license(new License()
                    .name("MIT")
                    .url("https://opensource.org/licenses/MIT")))
            .externalDocs(new ExternalDocumentation()
                .description("Wiki Documentation")
                .url("https://wiki.example.com"))
            .addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
            .components(new Components()
                .addSecuritySchemes("bearerAuth",
                    new SecurityScheme()
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("bearer")
                        .bearerFormat("JWT")
                        .description("JWT token authentication")));
    }
}

Controller Documentation

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "Users", description = "User management endpoints")
public class UserController {

    private final UserService userService;

    @Operation(
        summary = "Get all users",
        description = "Returns a paginated list of all users"
    )
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "Success",
            content = @Content(schema = @Schema(implementation = PageUserResponse.class))),
        @ApiResponse(responseCode = "401", description = "Unauthorized",
            content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
    })
    @GetMapping
    public ResponseEntity<Page<UserResponse>> findAll(
            @Parameter(description = "Page number (0-indexed)")
            @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "Page size")
            @RequestParam(defaultValue = "10") int size) {
        return ResponseEntity.ok(userService.findAll(page, size));
    }

    @Operation(summary = "Get user by ID")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "User found"),
        @ApiResponse(responseCode = "404", description = "User not found")
    })
    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> findById(
            @Parameter(description = "User ID", required = true, example = "1")
            @PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    @Operation(summary = "Create new user")
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "User created"),
        @ApiResponse(responseCode = "400", description = "Invalid input")
    })
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<UserResponse> create(
            @io.swagger.v3.oas.annotations.parameters.RequestBody(
                description = "User creation data",
                required = true,
                content = @Content(schema = @Schema(implementation = CreateUserRequest.class)))
            @Valid @RequestBody CreateUserRequest dto) {
        return ResponseEntity.status(HttpStatus.CREATED)
            .body(userService.create(dto));
    }
}

Schema Documentation

@Data
@Schema(description = "User creation request")
public class CreateUserRequest {

    @Schema(
        description = "User's full name",
        example = "John Doe",
        minLength = 2,
        maxLength = 100,
        requiredMode = Schema.RequiredMode.REQUIRED
    )
    @NotBlank
    @Size(min = 2, max = 100)
    private String name;

    @Schema(
        description = "User's email address",
        example = "john.doe@example.com",
        format = "email",
        requiredMode = Schema.RequiredMode.REQUIRED
    )
    @NotBlank
    @Email
    private String email;

    @Schema(
        description = "User's password",
        example = "SecurePass123!",
        minLength = 8,
        requiredMode = Schema.RequiredMode.REQUIRED,
        accessMode = Schema.AccessMode.WRITE_ONLY
    )
    @NotBlank
    @Size(min = 8)
    private String password;

    @Schema(
        description = "User's role",
        example = "USER",
        defaultValue = "USER",
        allowableValues = {"ADMIN", "MANAGER", "USER"}
    )
    private UserRole role;
}

@Data
@Builder
@Schema(description = "User response")
public class UserResponse {

    @Schema(description = "User ID", example = "1")
    private Long id;

    @Schema(description = "User's name", example = "John Doe")
    private String name;

    @Schema(description = "User's email", example = "john@example.com")
    private String email;

    @Schema(description = "User's role", example = "USER")
    private UserRole role;

    @Schema(description = "Account status", example = "ACTIVE")
    private UserStatus status;

    @Schema(description = "Creation timestamp", example = "2024-01-15T10:30:00")
    private LocalDateTime createdAt;
}

Enum Documentation

@Schema(description = "User roles")
public enum UserRole {
    @Schema(description = "System administrator with full access")
    ADMIN,

    @Schema(description = "Department manager")
    MANAGER,

    @Schema(description = "Regular user")
    USER
}

Security Documentation

// Mark endpoint as public (no auth required)
@Operation(summary = "Login", security = {})
@PostMapping("/auth/login")
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
    return ResponseEntity.ok(authService.login(request));
}

// Require specific roles in documentation
@Operation(
    summary = "Delete user",
    security = @SecurityRequirement(name = "bearerAuth")
)
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
    userService.delete(id);
    return ResponseEntity.noContent().build();
}

Group APIs by Tag

@Configuration
public class OpenApiConfig {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
            .group("public")
            .pathsToMatch("/api/v1/public/**")
            .build();
    }

    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
            .group("admin")
            .pathsToMatch("/api/v1/admin/**")
            .build();
    }
}

Hide Endpoints

@Hidden  // Hide entire controller
@RestController
public class InternalController { }

// Hide specific endpoint
@Operation(hidden = true)
@GetMapping("/internal")
public void internal() { }

Key Annotations

AnnotationPurpose
@TagGroup endpoints
@OperationDescribe endpoint
@ParameterDocument path/query param
@SchemaDocument model/field
@ApiResponseDocument response
@HiddenHide from docs
@SecurityRequirementSecurity scheme

When NOT to Use This Skill

  • General OpenAPI specification writing (use openapi skill)
  • GraphQL API documentation (use graphql skill)
  • Non-Spring Boot Java projects
  • Frontend OpenAPI client generation (use openapi-codegen skill)
  • Node.js/TypeScript APIs (use openapi or framework-specific skills)

Anti-Patterns

Anti-PatternWhy It's BadSolution
Missing @Operation on endpointsPoor documentation, no descriptionsAdd @Operation to all endpoints
No examples in @SchemaHard to understand APIAdd example values to all schemas
Exposing DTOs without @SchemaMissing field descriptionsDocument all DTO fields with @Schema
Not hiding internal endpointsExposes implementation detailsUse @Hidden for internal endpoints
Missing security requirementsUnclear auth requirementsAdd @SecurityRequirement where needed
No error response documentationIncomplete API contractDocument all 4xx/5xx responses
Using entity classes as API modelsLeaks database structureCreate separate DTO classes
Hardcoding API info in codeConfiguration not externalizedUse application.yml for API metadata
Missing @Parameter descriptionsPoor API usabilityDocument all path/query parameters

Quick Troubleshooting

IssuePossible CauseSolution
Swagger UI not loadingIncorrect springdoc configurationCheck springdoc.swagger-ui.path in application.yml
Endpoints not showingController not in scan pathCheck springdoc.packages-to-scan
Schema missing fieldsField is private without getterAdd getters or use @Schema on field
Security not working in UISecurity scheme not configuredAdd @SecurityScheme in config
Wrong base path in UIServer URL not configuredSet servers in OpenAPI config
Type errors in generated schemaDTO field type not supportedUse @Schema to specify format
Circular reference errorsSelf-referencing DTOsUse @Schema(ref = "#/components/schemas/X")
Missing enum valuesEnum not documentedAdd @Schema to enum fields
404 on /v3/api-docsspringdoc not enabledAdd springdoc dependency, check api-docs.enabled

Reference Documentation

Signals

GitHub stars
33
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
springdoc-openapi
Source
github.com/claude-dev-suite/claude-dev-suite