Swagger & OpenAPI 3 in Spring Boot: springdoc-openapi 2.x Complete Guide
Master Swagger/OpenAPI 3 in Spring Boot with springdoc-openapi 2.
- Add
springdoc-openapi-starter-webmvc-ui2.x dependency — no springfox, it's dead - Annotate controllers with
@Operation,@ApiResponse, and models with@Schemafor rich docs - Configure JWT Bearer security scheme in a
@BeanOpenAPIconfig — not per-controller - Use
@RouterOperationfor functional endpoints;springdocauto-discovers@RestControllerby default - Group APIs with
GroupedOpenApibeans and expose/v3/api-docs/group-nameper team or domain
OpenAPI is like a restaurant menu — it tells customers exactly what dishes (endpoints) are available, what ingredients (request parameters) each dish requires, and what they'll get back (responses). Swagger UI is the waiter who presents that menu in a friendly, interactive way so you can actually order (test) without writing code.
You've just onboarded three new frontend developers and a mobile team. They spend three days reverse-engineering your API by reading source code and firing random requests because the documentation is either outdated, missing, or locked in a Confluence page that hasn't been touched since 2022. That three-day onboarding tax is multiplied across every integration partner, every new hire, and every support ticket. Good API documentation pays for itself in hours.
Springfox was the de facto Spring Boot documentation tool for years, but development stalled after Spring Boot 2.6 broke its URL matching strategy and it was never properly updated for Spring Boot 3. Continuing to use springfox in 2024 means fighting with classpath conflicts, broken security scheme rendering, and missing OpenAPI 3.1 features. springdoc-openapi is the correct answer for Spring Boot 3.x.
But documentation is not just developer convenience. OpenAPI specs are machine-readable contracts. They power code generators that create client SDKs in 20 languages from a single YAML file. They enable contract testing with tools like Pact. They feed into API gateways like Kong and AWS API Gateway for automatic routing and authentication enforcement. A well-maintained OpenAPI spec is a core infrastructure asset.
The difference between a useful API spec and a noise-filled spec comes down to discipline: documenting error responses, providing realistic examples, marking deprecated fields, and keeping the spec in sync with the code. springdoc-openapi makes keeping it in sync nearly automatic — but the quality of the human-written annotations determines whether the spec is genuinely useful.
This guide covers the full springdoc-openapi 2.x setup: dependency configuration, annotation best practices, JWT Bearer security scheme, multi-group API organization, hiding internal endpoints, and CI validation with the OpenAPI Gradle plugin.
Getting Started: Dependency and Auto-Configuration
springdoc-openapi 2.x integrates with Spring Boot 3.x auto-configuration. Adding a single dependency triggers the entire Swagger/OpenAPI setup with sensible defaults. No @EnableSwagger2 annotation, no Docket bean, no XML configuration. The library scans all @RestController beans on startup and builds the OpenAPI spec lazily on the first request to /v3/api-docs.
The critical version match: springdoc-openapi-starter-webmvc-ui version 2.x is for Spring Boot 3.x (Jakarta EE). Version 1.x is for Spring Boot 2.x (javax). Mixing them causes ClassNotFoundException at startup. Check the springdoc version compatibility matrix before every Spring Boot upgrade.
For reactive Spring WebFlux applications, use springdoc-openapi-starter-webflux-ui instead. For applications with both MVC and WebFlux, you need both. For Spring Boot applications without a web UI (just the API doc endpoint), use springdoc-openapi-starter-webmvc-api to exclude the Swagger UI static resources from the JAR.
After adding the dependency, visit http://localhost:8080/swagger-ui/index.html. You should see a fully functional Swagger UI with all your controllers documented. The spec is also available at /v3/api-docs (JSON) and /v3/api-docs.yaml. Configure the title, version, and description by adding an OpenAPI Spring bean.
Annotating Controllers: @Operation, @ApiResponse, @Parameter
Auto-generated specs are a starting point, not a destination. Without annotations, springdoc generates technically correct but human-useless documentation: endpoint paths and types but no descriptions, no error response documentation, no examples, no deprecation notices. The annotations @Operation, @ApiResponse, @Parameter, and @RequestBody (from springdoc, not Spring) close this gap.
@Operation annotates a controller method and provides a summary (one-line description shown in the endpoint list) and description (multi-line Markdown shown when the endpoint is expanded). Always fill both. A good summary answers 'what does this do?' A good description answers 'when should I use this, what are the side effects, and what can go wrong?'
@ApiResponse documents possible HTTP response codes. Document at minimum: 200/201 (success), 400 (validation error), 401 (unauthenticated), 403 (unauthorized), 404 (not found), 422 (business rule violation), and 500 (server error). For each, provide a description and a content with an example response body. Undocumented error responses cause integration partners to hardcode 200-or-crash logic.
@Parameter annotates path variables, query params, and headers. Use it to add descriptions, mark required/optional, and provide example values. Example values are rendered in Swagger UI as pre-filled inputs — they reduce friction for developers trying the API for the first time.
Annotate deprecated endpoints with @Operation(deprecated = true) and include a migration note in the description explaining what to use instead and when the old endpoint will be removed.
Schema Annotation: @Schema on DTOs and Enums
Request and response DTOs are the heart of your API contract. @Schema on model classes and fields adds descriptions, examples, format constraints, and deprecation markers to the generated spec. Well-annotated DTOs eliminate most onboarding questions from integration partners.
At the class level, @Schema(description = '...') adds a description to the schema object in the spec. At the field level, @Schema(description = '...', example = '...') adds field-level documentation and an example value that appears in Swagger UI's example request body. Use @Schema(requiredMode = RequiredMode.REQUIRED) to mark fields as required in the schema — this is separate from Bean Validation's @NotNull.
For enums, springdoc renders all enum values in the spec by default. Add @Schema(description = '...') to the enum class and to individual constants where the semantics aren't obvious. For complex enums (state machines), add a table in the class-level description explaining valid transitions.
For fields that accept multiple formats (e.g., a date that can be epoch millis or ISO 8601 string), document both with @Schema(description = 'Accepts epoch millis or ISO 8601', example = '2024-01-15T10:30:00Z'). Ambiguous formats in APIs are a permanent source of integration bugs.
Mark deprecated fields with @Schema(deprecated = true) and add a description explaining the replacement. Never remove deprecated fields without a major version bump — always deprecate first and give partners at least 6 months notice.
JWT Bearer Security Scheme Configuration
Most production APIs use JWT Bearer token authentication. The OpenAPI security scheme configuration tells Swagger UI to show an Authorize button and attach the Bearer token to every request. Without this, developers must manually edit request headers in Swagger UI for every single request — frustrating and error-prone.
The security scheme is defined in the global OpenAPI bean under components.securitySchemes. The scheme name (e.g., bearerAuth) must match exactly in both the scheme definition and the @SecurityRequirement annotations on operations. Case sensitivity matters.
For APIs with both public and authenticated endpoints, don't apply the security requirement globally — apply it per-controller or per-method with @SecurityRequirement(name = 'bearerAuth'). Public endpoints (like login, signup, health check) should not show the padlock icon.
For APIs with multiple authentication methods (API key + JWT, or different token types for different consumer groups), define multiple security schemes and apply different ones to different groups of endpoints. springdoc supports OR (any one scheme) and AND (multiple required) security requirement configurations.
For OAuth2 flows, use SecurityScheme.Type.OAUTH2 with OAuthFlows. springdoc renders the full OAuth2 authorization code flow in Swagger UI, allowing developers to authenticate directly from the docs without copying tokens manually.
API Grouping with GroupedOpenApi
As APIs grow, a single Swagger UI showing 200+ endpoints from 15 different domains becomes unusable. GroupedOpenApi beans split the spec into logical groups, each with its own /v3/api-docs/{group-name} endpoint and its own Swagger UI tab. This is the production-grade approach for any API with more than 3-4 controllers.
Groups are defined by path prefix, package name, or annotation. Path prefix is the most common: /api/v1/ is one group, /api/v2/ is another. Package-based grouping works well for microservices where different teams own different controller packages.
Each GroupedOpenApi can have its own OpenApiCustomizer that applies group-specific metadata: a different title, a different base URL, additional servers, or group-specific security requirements. This lets you maintain a public API group (consumer-facing) and a private API group (partner/internal) with different security schemes and documentation depth.
For microservices using an API gateway, configure springdoc on the gateway to aggregate specs from multiple services using the urls configuration in Swagger UI. The gateway's Swagger UI shows specs from all downstream services in a single browser tab, making cross-service development dramatically easier.
mvn springdoc:generate and use openapi-diff in the pipeline — breaking changes to the public group spec fail the build automatically.Generating the Spec in CI and Contract Testing
Runtime spec generation is convenient for development but unreliable for CI/CD. A spec generated in a JVM with a mocked database is not guaranteed to match the running production service. The correct approach generates the spec as a build artifact, commits it to the repository, and validates every PR against it.
The springdoc-openapi-maven-plugin generates the spec at build time by starting the Spring context in a test profile. The output is a .json or .yaml file that can be committed to the repository. CI then uses openapi-diff to compare the committed spec against the newly generated one and fails the build if breaking changes are introduced without a version bump.
Contract testing with Pact uses the OpenAPI spec as the consumer contract. The provider (your Spring Boot service) runs Pact verifications against the spec in CI, ensuring every documented endpoint actually works as documented. This catches drift between annotations and implementation — the most common documentation bug.
For SDK generation, the committed spec feeds into openapi-generator-cli in a separate CI step that regenerates client libraries in TypeScript, Python, and Go. If the spec changes in a way that breaks client generation, CI catches it. The generated clients are published to an internal package registry.
Why Your Devs Are Yelling at the Swagger UI
Out of the box, Springdoc gives you a working Swagger UI. But the default config will sink you in production. You get every endpoint exposed, every schema dumped, and no way to control what hits the wire. That's a security incident waiting to happen. The fix is surgical: disable the UI in production profiles, control which groups are visible, and always set a custom path. Never expose /v3/api-docs or /swagger-ui/* to the open internet. Use springdoc.api-docs.enabled=false in your production profile and route through a gateway that enforces auth. Your QA team will thank you. Your security auditor will too.
JSR-303 Validation: The Silent Spec Killer
You spent hours annotating controllers with @Operation, @ApiResponse, and @Schema. That's fine, but you're duplicating work. Springdoc auto-discovers JSR-303 Bean Validation annotations (@NotNull, @Size, @Min, @Pattern) on your DTOs and injects them into the OpenAPI spec. No extra code needed. This means when you add @NotBlank on a field, Swagger UI shows 'required: true' and the minLength constraint automatically. If you change validation logic, the spec updates. No sync issues. But — only if you don't override it manually with @Schema. If you mix both, Springdoc prefers the explicit @Schema annotation. That's fine, but you lose the auto-sync. Pick one: use validation annotations for the contract, or use @Schema. Don't mix unless you're debugging something exotic.
Security Breach: OpenAPI Spec Exposed in Production
- Never expose OpenAPI docs in production without authentication.
- Add a security integration test that asserts /v3/api-docs returns 403 in the production profile.
- Use @Hidden on all internal endpoints and enforce it with an ArchUnit rule.
springdoc.packages-to-scan configuration; (2) all controllers are annotated with @RestController or @Controller — plain @Component classes are not scanned; (3) the springdoc dependency version matches your Spring Boot version — use springdoc-openapi-starter-webmvc-ui 2.x for Spring Boot 3.x.PI.addSecurityItem(). In Swagger UI, use the Authorize button and enter only the token — not 'Bearer token'.curl -s http://localhost:8080/v3/api-docs | jq '.paths | keys'curl -s http://localhost:8080/v3/api-docs | jq '.info'Key takeaways
Common mistakes to avoid
6 patternsUsing springfox with Spring Boot 3.x
Leaving Swagger UI enabled in production
Not documenting error responses
Applying global security to public endpoints
Using @Hidden on all sensitive data but not restricting at the security layer
Not providing realistic example values in @Schema
Interview Questions on This Topic
What is the difference between springfox and springdoc-openapi, and which should you use for Spring Boot 3?
Frequently Asked Questions
That's Spring Boot. Mark it forged?
8 min read · try the examples if you haven't