Spring Boot OpenAPI 3 & Swagger UI
springdoc-openapi scans your Spring Boot application at startup and automatically generates an OpenAPI 3 spec from your @RestController methods, @RequestBody / @PathVariable parameters, and return types. Adding one dependency gets you a live JSON spec at /v3/api-docs and an interactive Swagger UI at /swagger-ui.html: no XML or YAML spec files to maintain manually.
Contents
Dependency & Quick Start
Add a single dependency: springdoc-openapi does the rest. The Swagger UI is available immediately without any extra configuration.
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<!-- For WebFlux applications use: -->
<!-- springdoc-openapi-starter-webflux-ui -->
# Swagger UI URL: /swagger-ui.html → http://localhost:8080/swagger-ui.html
# OpenAPI JSON: /v3/api-docs → http://localhost:8080/v3/api-docs
# Optional: change the paths
springdoc.swagger-ui.path=/docs
springdoc.api-docs.path=/api-spec
# Disable in production
springdoc.swagger-ui.enabled=false
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()Global API Info
Define title, version, description, and contact info for the entire API in one place using an OpenAPI
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Order Service API")
.version("1.0.0")
.description("Manages orders, products, and customer accounts.")
.contact(new Contact()
.name("CsCode.io Team")
.email("api@example.com")
.url("https://example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")));
}
}
Annotating Endpoints
Use @Operation@ApiResponse
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
@RestController
@RequestMapping("/orders")
@Tag(name = "Orders", description = "Operations for managing orders")
public class OrderController {
@Operation(summary = "Get an order by ID",
description = "Returns a single order matching the given ID.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Order found"),
@ApiResponse(responseCode = "404", description = "Order not found"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(
@Parameter(description = "The order ID", example = "ORD-12345")
@PathVariable String id) {
return orderService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@Operation(summary = "Create a new order")
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Order createOrder(@RequestBody Order order) {
return orderService.save(order);
}
}
Schema & Model Annotations
Annotate your model classes with @Schema
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "Represents a customer order")
public class Order {
@Schema(description = "Unique order ID", example = "ORD-12345", accessMode = Schema.AccessMode.READ_ONLY)
private String id;
@Schema(description = "Product name", example = "Laptop", required = true)
private String product;
@Schema(description = "Quantity ordered", example = "2", minimum = "1")
private int quantity;
@Schema(description = "Current order status", allowableValues = {"PENDING","PROCESSING","COMPLETED","CANCELLED"})
private String status;
// getters/setters
}
Documenting Security (JWT Bearer)
Declare a Bearer token security scheme in the OpenAPI
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.Components;
@Bean
public OpenAPI customOpenAPI() {
final String securitySchemeName = "bearerAuth";
return new OpenAPI()
.info(new Info().title("Order API").version("1.0"))
// Apply Bearer auth globally to all endpoints
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
.components(new Components()
.addSecuritySchemes(securitySchemeName,
new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
// Exclude specific endpoints from the security requirement
@Operation(summary = "Public health check", security = {})
@GetMapping("/health")
public String health() { return "OK"; }
API Groups
Group different sets of endpoints under separate Swagger UI tabs using GroupedOpenApi
import org.springdoc.core.models.GroupedOpenApi;
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.displayName("Public API")
.pathsToMatch("/orders/**", "/products/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.displayName("Admin API")
.pathsToMatch("/admin/**")
.build();
}
Customization & Config
Key application.properties
# Sort endpoints alphabetically in Swagger UI
springdoc.swagger-ui.operationsSorter=alpha
# Show request duration after each try-it-out call
springdoc.swagger-ui.display-request-duration=true
# Expand all operations by default
springdoc.swagger-ui.doc-expansion=full
# Include only specific packages in the spec
springdoc.packages-to-scan=com.example.api
# Exclude actuator endpoints from the spec
springdoc.paths-to-exclude=/actuator/**
# Display the try-it-out button for GET requests only
springdoc.swagger-ui.supported-submit-methods=get
springdoc.swagger-ui.enabled=false