How to use Swagger with Spring Boot
Swagger is a set of tools used to document, visualize and test REST APIs. In Spring Boot applications, Swagger helps developers and testers understand the available APIs and their request and response structures, and try the APIs directly from a browser.
This guide shows how to add Swagger to a Spring Boot application with springdoc-openapi, document endpoints and models with annotations, customize and group the documentation, document JWT security and error responses, and secure Swagger in production, with formatted Java and configuration examples. It works well together with Spring Boot REST API and Spring Boot Validation.
1. What Are Swagger and OpenAPI?
OpenAPI is a standard, language-independent format for describing REST APIs: which endpoints exist, which parameters and request bodies they accept, and which responses they return. The description is a JSON or YAML document called an OpenAPI specification.
Swagger is the name of a popular set of tools built around that format, such as Swagger UI, which turns the specification into an interactive web page. The format itself was originally called the Swagger Specification and was renamed the OpenAPI Specification in 2016; the tools kept the Swagger name.
In Spring Boot, the most commonly used library is springdoc-openapi. It reads your controllers, generates the OpenAPI specification automatically, and serves Swagger UI for it.
2. Why Use Swagger in Spring Boot?
- Automatically generates API documentation from your code.
- Provides an interactive UI (Swagger UI) to test APIs without extra tools.
- Improves collaboration between frontend, backend and QA teams.
- Reduces manual documentation effort and keeps docs in sync with the code.
- Helps new developers understand APIs quickly.
- Produces a standard specification that tools can use to generate client code.
3. Swagger vs OpenAPI
| OpenAPI | Swagger | |
|---|---|---|
| What it is | A specification (a standard format) for describing REST APIs | A set of tools that work with that specification |
| Examples | OpenAPI 3.0 and 3.1 documents in JSON or YAML | Swagger UI, Swagger Editor, Swagger Codegen |
| In Spring Boot | Generated by springdoc at /v3/api-docs | Swagger UI page at /swagger-ui/index.html |
In short: Spring Boot produces an OpenAPI specification and shows it with Swagger UI. People often use “Swagger” to mean both.
4. How springdoc-openapi Works
Your controllers and models
(@RestController, @GetMapping, records, validation annotations)
|
v springdoc scans them at startup
OpenAPI specification -> http://localhost:8080/v3/api-docs (JSON)
|
v rendered by
Swagger UI -> http://localhost:8080/swagger-ui/index.html
springdoc reads Spring MVC annotations such as @GetMapping, @PathVariable and @RequestBody, Bean Validation annotations such as @NotBlank, and optional Swagger annotations such as @Operation. You only add annotations where you want more detail.
5. Step 1: Add the springdoc-openapi Dependency
Choose the springdoc version that matches your Spring Boot version:
| Spring Boot | springdoc-openapi | Artifact |
|---|---|---|
| 4.x | 3.x | springdoc-openapi-starter-webmvc-ui |
| 3.x | 2.x | springdoc-openapi-starter-webmvc-ui |
| 2.x (end of life) | 1.x | springdoc-openapi-ui |
<properties>
<!-- use the latest springdoc release for your Spring Boot version -->
<springdoc.version>2.8.9</springdoc.version>
</properties>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
}
Once this dependency is added, Swagger is enabled automatically; no extra configuration is needed for basic use. For reactive applications built with WebFlux, use springdoc-openapi-starter-webflux-ui instead. If you only want the JSON specification without the UI, use springdoc-openapi-starter-webmvc-api.
6. Step 2: Run the Application and Open Swagger UI
Start your Spring Boot application normally. After startup, these addresses are available:
| URL | What you get |
|---|---|
http://localhost:8080/swagger-ui.html | Short address that redirects to Swagger UI |
http://localhost:8080/swagger-ui/index.html | The Swagger UI page |
http://localhost:8080/v3/api-docs | The OpenAPI specification as JSON |
http://localhost:8080/v3/api-docs.yaml | The same specification as YAML |
You will see a web page listing all available REST APIs. If your application uses a context path such as /shop, add it before these paths, for example http://localhost:8080/shop/swagger-ui.html.
7. Step 3: Create a Sample REST Controller
springdoc automatically scans classes annotated with @RestController and documents their endpoints. This small user API uses Java records for the request and response bodies:
package com.example.users;
public record UserRequest(String name, String email) {
}
package com.example.users;
public record UserResponse(long id, String name, String email) {
}
package com.example.users;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final Map<Long, UserResponse> users = new ConcurrentHashMap<>();
private final AtomicLong ids = new AtomicLong();
@GetMapping
public List<UserResponse> getAllUsers() {
return List.copyOf(users.values());
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable long id) {
UserResponse user = users.get(id);
return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse createUser(@RequestBody UserRequest request) {
long id = ids.incrementAndGet();
UserResponse user = new UserResponse(id, request.name(), request.email());
users.put(id, user);
return user;
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable long id) {
users.remove(id);
}
}
Once the application is running, these four endpoints appear in Swagger UI automatically, together with the UserRequest and UserResponse structures. The map is only a simple in-memory store for the example; a real application would use a service and a database.
8. Step 4: Understanding Swagger UI
Swagger UI provides the following features:
- A list of all REST endpoints, grouped by controller (or by tag).
- HTTP methods (GET, POST, PUT, DELETE) shown with colours.
- Request parameters and request body, with example values.
- Response status codes and response models.
- A Try it out button to call APIs live and see the real response.
- A Schemas section listing every request and response model.
- An Authorize button when security is documented (see Step 10).
9. Step 5: Adding Swagger Annotations
Swagger annotations improve the documentation by adding summaries, descriptions, examples and response details. They come from the io.swagger.v3.oas.annotations package, which is included with springdoc.
| Annotation | Used on | Purpose |
|---|---|---|
@Tag | Controller class | Groups endpoints under a name and description |
@Operation | Method | Summary and description of an endpoint |
@Parameter | Method parameter | Describes a path, query or header parameter |
@ApiResponse / @ApiResponses | Method | Documents possible status codes and their bodies |
@Schema | Class or field | Describes a model or one of its fields |
@Hidden | Class or method | Hides an endpoint from the documentation |
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("/api/users")
@Tag(name = "Users", description = "Create, read and delete users")
public class UserController {
@Operation(summary = "Get user by ID", description = "Returns one user using the user ID")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "User found"),
@ApiResponse(responseCode = "404", description = "User not found")
})
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(
@Parameter(description = "ID of the user", example = "101")
@PathVariable long id) {
UserResponse user = users.get(id);
return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();
}
}
Swagger also has its own @RequestBody annotation for describing request bodies. Because Spring has an annotation with the same name, use the full name (io.swagger.v3.oas.annotations.parameters.RequestBody) if you need both on the same method.
10. Step 6: Documenting Request and Response Models
springdoc documents request and response classes automatically. Add @Schema to describe fields and give example values. Bean Validation annotations are also picked up, so required fields and size limits appear in the documentation:
package com.example.users;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Schema(description = "Data needed to create a user")
public record UserRequest(
@Schema(description = "Full name of the user", example = "Asha Verma")
@NotBlank
@Size(max = 100)
String name,
@Schema(description = "Email address", example = "asha@example.com")
@NotBlank
@Email
String email) {
}
package com.example.users;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "A user stored in the system")
public record UserResponse(
@Schema(description = "Unique user ID", example = "101", accessMode = Schema.AccessMode.READ_ONLY)
long id,
@Schema(description = "Full name of the user", example = "Asha Verma")
String name,
@Schema(description = "Email address", example = "asha@example.com")
String email) {
}
These details appear in the Schemas section of Swagger UI and in the example request body under Try it out. Add @Valid to the @RequestBody parameter in the controller so the validation rules are also enforced at runtime.
11. Step 7: Customizing API Information
Define an OpenAPI bean to set the API title, description, version, contact, licence and server addresses shown at the top of Swagger UI:
package com.example.config;
import java.util.List;
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 io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI userManagementOpenApi() {
return new OpenAPI()
.info(new Info()
.title("User Management API")
.version("1.0")
.description("API documentation for the User Management System")
.contact(new Contact().name("API Team").email("api-team@example.com"))
.license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0")))
.servers(List.of(
new Server().url("http://localhost:8080").description("Local"),
new Server().url("https://api.example.com").description("Production")));
}
}
12. Step 8: Configuring springdoc with Properties
Many springdoc settings can be changed in application.yml without code:
springdoc:
api-docs:
path: /v3/api-docs # where the JSON specification is served
swagger-ui:
path: /swagger-ui.html # short address for Swagger UI
operations-sorter: method # sort endpoints by HTTP method
tags-sorter: alpha # sort tags alphabetically
try-it-out-enabled: true # open "Try it out" by default
packages-to-scan: com.example # only document controllers in this package
paths-to-match: /api/** # only document these paths
| Property | Purpose |
|---|---|
springdoc.api-docs.path | Changes the address of the JSON specification |
springdoc.swagger-ui.path | Changes the address of Swagger UI |
springdoc.packages-to-scan | Limits documentation to certain packages |
springdoc.paths-to-match | Limits documentation to certain URL patterns |
springdoc.show-actuator | Includes Actuator endpoints in the documentation |
springdoc.api-docs.enabled / springdoc.swagger-ui.enabled | Turns the specification or the UI on or off |
13. Step 9: Grouping APIs
Large applications can split their documentation into groups, for example public and admin APIs. Each group gets its own specification, and Swagger UI shows a drop-down to switch between them:
package com.example.config;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApiGroupsConfig {
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.pathsToMatch("/api/users/**", "/api/products/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/api/admin/**")
.build();
}
}
Each group’s specification is available at /v3/api-docs/{group}, for example /v3/api-docs/public.
14. Step 10: Documenting JWT Authentication
If your API uses JWT bearer tokens, describe the security scheme so Swagger UI shows an Authorize button. After entering a token once, every “Try it out” request sends it in the Authorization header.
package com.example.config;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springframework.context.annotation.Configuration;
@Configuration
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
scheme = "bearer",
bearerFormat = "JWT")
public class OpenApiSecurityConfig {
}
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@RestController
@RequestMapping("/api/users")
@SecurityRequirement(name = "bearerAuth") // all endpoints here need a token
public class UserController {
// ...
}
This only documents the security. The actual protection still comes from your Spring Security configuration. See Spring Security for setting that up.
15. Step 11: Documenting Error Responses
Clients need to know what an error looks like, not only the successful response. Spring Boot 3 can return errors in the standard Problem Details format (ProblemDetail), which you can document like this:
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.http.ProblemDetail;
@Operation(summary = "Create a user")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "User created"),
@ApiResponse(responseCode = "400", description = "Invalid input",
content = @Content(mediaType = "application/problem+json",
schema = @Schema(implementation = ProblemDetail.class)))
})
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse createUser(@Valid @RequestBody UserRequest request) {
long id = ids.incrementAndGet();
UserResponse user = new UserResponse(id, request.name(), request.email());
users.put(id, user);
return user;
}
If the same error responses apply to many endpoints, document them once in an @RestControllerAdvice class; springdoc includes the responses declared by exception handlers there. See Spring Boot Exception Handling.
16. Step 12: Securing or Disabling Swagger in Production
In production environments, Swagger UI should be disabled or protected, because it reveals every endpoint and lets visitors call them. Two common options:
Option 1: Disable Swagger with a production profile
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false
Start the application with --spring.profiles.active=prod and both the specification and the UI are switched off. See Spring Boot Profiles.
Option 2: Protect Swagger with Spring Security
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// only administrators may open the API documentation
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().denyAll())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
If you want Swagger to be public in a development environment, replace hasRole("ADMIN") with permitAll(). Forgetting to allow these paths is the most common reason Swagger UI shows a login page or a 401 error.
17. Exporting the OpenAPI Specification
The generated specification is useful outside the application too: for API reviews, contract tests, publishing on a developer portal, or generating client code.
# Download the specification while the application is running
curl -s http://localhost:8080/v3/api-docs.yaml -o openapi.yaml
Tools such as OpenAPI Generator can read openapi.yaml and produce client libraries in Java, TypeScript, Python and many other languages. Keep the file in version control to review API changes in pull requests.
18. Common Problems and Fixes
| Problem | Likely cause | Fix |
|---|---|---|
| Swagger UI returns 404 | Wrong artifact for your Spring Boot version, or a context path | Use the matching springdoc version from Step 1; add the context path to the URL |
| Swagger UI shows a login page or 401 | Spring Security blocks the documentation paths | Allow /v3/api-docs/**, /swagger-ui/** and /swagger-ui.html |
| Some endpoints are missing | packages-to-scan or paths-to-match is too narrow, or @Hidden is used | Check the springdoc properties and annotations |
| Request body shows as a plain string or object | Endpoint uses String, Map or Object | Use a typed record or class for the body |
| Application fails to start after adding springdoc | springdoc version does not match the Spring Boot version | Upgrade or downgrade springdoc to the matching line |
| Try it out calls the wrong host | Application runs behind a proxy | Set the correct server URL in the OpenAPI bean or configure forwarded headers |
19. Best Practices for Using Swagger in Spring Boot
| Do | Avoid |
|---|---|
| Add meaningful summaries and descriptions to every endpoint | Leaving endpoints with only their method names |
| Document error responses clearly | Documenting only the 200 response |
| Use examples for request and response bodies | Empty example values that hide the real format |
| Use typed request and response classes | Map or String bodies that produce vague documentation |
| Disable or secure Swagger in production | Leaving Swagger UI open to the internet |
| Version your API and keep the documentation in sync | Hand-written documents that drift from the code |
| Use the springdoc version that matches Spring Boot | Mixing incompatible versions |
| Export the specification and review changes | Breaking clients without noticing |
20. Conclusion
Swagger and OpenAPI are essential tools for modern Spring Boot applications. With a single springdoc dependency, Spring Boot generates a standard OpenAPI specification from your controllers and shows it in interactive Swagger UI. Annotations such as @Operation, @Schema and @ApiResponse add detail, an OpenAPI bean and properties customize it, groups split large APIs, and security schemes enable token-based testing. By integrating Swagger, teams improve development speed, reduce misunderstandings and make their APIs easier to use.
For REST-based microservices, API documentation is no longer optional; keeping it generated from code with Swagger is a best practice. Just remember to disable or protect it in production.
21. Frequently Asked Questions (FAQ)
Q1. What is Swagger in Spring Boot?
Swagger is a set of tools that shows your REST API documentation as an interactive web page. In Spring Boot, the springdoc-openapi library generates an OpenAPI specification from your controllers and displays it with Swagger UI, where you can read about each endpoint and test it directly from the browser.
Q2. What is the difference between Swagger and OpenAPI?
OpenAPI is the specification: a standard JSON or YAML format for describing REST APIs. Swagger is a family of tools, such as Swagger UI and Swagger Editor, that work with OpenAPI documents. The format was called the Swagger Specification until 2016, which is why the names are often mixed up.
Q3. Which dependency should I use for Swagger in Spring Boot 3?
Use springdoc-openapi-starter-webmvc-ui from the 2.x line:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.9</version>
</dependency>
Spring Boot 4 needs springdoc 3.x, and WebFlux applications use springdoc-openapi-starter-webflux-ui.
Q4. Can I still use Springfox with Spring Boot 3?
No. Springfox is no longer maintained and does not work with Spring Boot 3. Use springdoc-openapi instead. Migration is mostly replacing old annotations such as @Api and @ApiOperation with @Tag and @Operation.
Q5. What is the Swagger UI URL in Spring Boot?
By default, http://localhost:8080/swagger-ui.html, which redirects to http://localhost:8080/swagger-ui/index.html. The raw specification is at /v3/api-docs (JSON) and /v3/api-docs.yaml (YAML). Add your context path if the application has one.
Q6. Do I need annotations for Swagger to work?
No. springdoc documents every @RestController endpoint automatically, including parameters, request bodies and response types. Annotations such as @Operation and @Schema are optional; they add summaries, descriptions and examples that make the documentation clearer.
Q7. How do I change the title and version shown in Swagger UI?
Define an OpenAPI bean:
@Bean
public OpenAPI apiInfo() {
return new OpenAPI().info(new Info()
.title("User Management API")
.version("1.0")
.description("APIs for managing users"));
}
Q8. How do I change the Swagger UI path?
Set it in application.yml or application.properties:
springdoc.swagger-ui.path=/docs
springdoc.api-docs.path=/api-docs
Swagger UI is then reached through /docs, and the specification at /api-docs.
Q9. How do I hide an endpoint from Swagger?
Add @Hidden to the method or the whole controller class. To exclude groups of endpoints, use springdoc.paths-to-exclude or narrow the documented area with springdoc.packages-to-scan and springdoc.paths-to-match.
Q10. How do I add example values to request and response models?
Use @Schema on fields or record components:
@Schema(description = "Email address", example = "asha@example.com")
String email
Swagger UI uses these examples in the Schemas section and in the request body shown by “Try it out”.
Q11. Does Swagger show Bean Validation rules?
Yes. springdoc reads annotations such as @NotNull, @NotBlank, @Size, @Min, @Max and @Pattern, and shows them as required fields, length limits and value ranges in the model schema.
Q12. How do I test secured APIs with a JWT token in Swagger UI?
Declare a bearer security scheme with @SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, scheme = "bearer", bearerFormat = "JWT") and mark controllers with @SecurityRequirement(name = "bearerAuth"). Swagger UI then shows an Authorize button where you paste the token once.
Q13. Why does Swagger UI ask me to log in or return 401?
Spring Security is protecting the documentation paths. Allow them explicitly:
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
In production, prefer hasRole("ADMIN") or disable Swagger instead of making it public.
Q14. Why do I get a 404 for swagger-ui.html?
The most common causes are a springdoc version that does not match your Spring Boot version, using the API-only artifact without the UI, a custom springdoc.swagger-ui.path, or a context path that is missing from the URL. Check the dependency first.
Q15. Should Swagger be enabled in production?
Usually not publicly. It reveals every endpoint and lets anyone try them. Disable it in the production profile with springdoc.api-docs.enabled=false and springdoc.swagger-ui.enabled=false, or restrict it to administrators with Spring Security.
Q16. How can I split a large API into several Swagger pages?
Create GroupedOpenApi beans, each with a group name and path patterns, for example a public group for /api/** and an admin group for /admin/**. Swagger UI shows a drop-down to switch between groups.
Q17. How do I document error responses?
Use @ApiResponse with a status code, description and, if needed, a body schema such as ProblemDetail. Errors shared by many endpoints can be declared once in a @RestControllerAdvice class, which springdoc also reads.
Q18. Can I generate client code from the Swagger documentation?
Yes. Download the specification from /v3/api-docs.yaml and use a tool such as OpenAPI Generator to create clients in Java, TypeScript, Python and many other languages. This keeps clients consistent with the server’s API.
Q19. Does Swagger work with Spring WebFlux?
Yes. Use springdoc-openapi-starter-webflux-ui instead of the WebMVC starter. Annotations, the OpenAPI bean, groups and properties work the same way for reactive controllers and functional routes.
Q20. Can Swagger document API versions such as /v1 and /v2?
Yes. springdoc documents every mapped path, so /api/v1/users and /api/v2/users both appear. For clarity, create one GroupedOpenApi per version with pathsToMatch("/api/v1/**") and pathsToMatch("/api/v2/**").