Hand-maintained API docs go stale. SpringDoc OpenAPI generates accurate, interactive documentation from your code. Learn annotations, security schemes, versioning, and customization.
JOptimize Team
API documentation that doesn't match the actual API is worse than no documentation. It misleads consumers, breaks integrations, and creates support tickets. SpringDoc OpenAPI generates documentation directly from your Spring Boot code — it can't drift because it's derived from the same annotations that drive your actual endpoints.
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.5.0</version> </dependency>
With zero configuration, this exposes:
/swagger-ui.html — interactive UI/v3/api-docs — raw OpenAPI JSON/v3/api-docs.yaml — OpenAPI YAML@RestController @RequestMapping("/api/v1/orders") @Tag(name = "Orders", description = "Order management endpoints") @RequiredArgsConstructor public class OrderController { @Operation( summary = "Get order by ID", description = "Returns a single order with all line items. Requires authentication." ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "Order found", content = @Content(schema = @Schema(implementation = OrderDto.class))), @ApiResponse(responseCode = "404", description = "Order not found", content = @Content(schema = @Schema(implementation = ErrorResponse.class))), @ApiResponse(responseCode = "403", description = "Access denied") }) @GetMapping("/{id}") public ResponseEntity<OrderDto> getOrder( @Parameter(description = "Order ID", example = "42", required = true) @PathVariable Long id) { return ResponseEntity.ok(orderService.findById(id)); } @Operation(summary = "Create a new order") @PostMapping @ResponseStatus(HttpStatus.CREATED) public OrderDto createOrder( @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "Order creation request", required = true ) @RequestBody @Valid CreateOrderRequest request) { return orderService.create(request); } }
@Schema(description = "Order creation request") public record CreateOrderRequest( @Schema(description = "Customer ID", example = "123", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull Long customerId, @Schema(description = "List of order items", minItems = 1) @NotEmpty List<OrderItemRequest> items, @Schema(description = "ISO 4217 currency code", example = "USD", defaultValue = "USD") @Pattern(regexp = "[A-Z]{3}") String currency ) {} @Schema(description = "Order item") public record OrderItemRequest( @Schema(description = "Product ID", example = "456") @NotNull Long productId, @Schema(description = "Quantity", minimum = "1", maximum = "100") @Min(1) @Max(100) int quantity ) {}
@Configuration public class OpenApiConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() .info(new Info() .title("JOptimize API") .description("Java performance monitoring and analysis API") .version("v1.0") .contact(new Contact() .name("JOptimize Team") .url("https://joptimize.io") .email("support@joptimize.io")) .license(new License() .name("MIT") .url("https://opensource.org/licenses/MIT"))) .addSecurityItem(new SecurityRequirement().addList("bearerAuth")) .components(new Components() .addSecuritySchemes("bearerAuth", new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") .bearerFormat("JWT") .description("JWT token obtained from /api/v1/auth/login"))); } }
With this, the Swagger UI shows an Authorize button where users enter their JWT token — all subsequent requests include it automatically.
// Group by version — separate docs for v1 and v2 @Configuration public class OpenApiGroupsConfig { @Bean public GroupedOpenApi v1Api() { return GroupedOpenApi.builder() .group("v1") .pathsToMatch("/api/v1/**") .build(); } @Bean public GroupedOpenApi v2Api() { return GroupedOpenApi.builder() .group("v2") .pathsToMatch("/api/v2/**") .build(); } }
Dropdowns in Swagger UI let users switch between /v3/api-docs/v1 and /v3/api-docs/v2.
// Exclude specific endpoints @Hidden // On controller or method @GetMapping("/internal/debug") public DebugInfo debug() { ... }
# application.properties — exclude actuator from API docs springdoc.paths-to-exclude=/actuator/** springdoc.show-actuator=false # Only show in non-production profiles springdoc.swagger-ui.enabled=${SWAGGER_ENABLED:false}
Security tip: disable Swagger UI in production (SWAGGER_ENABLED=false in prod env vars) — it exposes your full API surface.
@Schema(description = "Order status") public enum OrderStatus { @Schema(description = "Order received, awaiting payment") PENDING, @Schema(description = "Payment confirmed") PAID, @Schema(description = "Order picked and shipped") SHIPPED, @Schema(description = "Order delivered to customer") DELIVERED, @Schema(description = "Order cancelled by customer or system") CANCELLED }
This renders enum values with descriptions in the Swagger UI — instead of just ["PENDING", "PAID", "SHIPPED"], users see what each value means.
@ApiResponse for those codes leaves consumers guessing the error schemaMap<String, Object> as request/response types — generates useless {} schema; always use typed DTOs@Parameter on path variables — without it, the Swagger UI shows a plain text box with no type or exampleSpringDoc OpenAPI generates accurate, interactive API documentation directly from your Spring Boot code. Configure security schemes for JWT bearer auth, group endpoints by API version, add examples to DTOs with @Schema, and disable the UI in production. The investment in annotation coverage pays off every time a frontend developer or API consumer doesn't need to ask "what does this endpoint return on a 404?".
JOptimize flags undocumented endpoints, missing error response declarations, and raw Map types in REST controllers that produce useless OpenAPI schemas.
Generate API docs that never go stale — free scan.
Master Spring Boot, security, and Java performance with hands-on courses.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.