00:00

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

OpenAPISwagger
What it isA specification (a standard format) for describing REST APIsA set of tools that work with that specification
ExamplesOpenAPI 3.0 and 3.1 documents in JSON or YAMLSwagger UI, Swagger Editor, Swagger Codegen
In Spring BootGenerated by springdoc at /v3/api-docsSwagger 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

From code to documentation
  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 Bootspringdoc-openapiArtifact
4.x3.xspringdoc-openapi-starter-webmvc-ui
3.x2.xspringdoc-openapi-starter-webmvc-ui
2.x (end of life)1.xspringdoc-openapi-ui
pom.xml
<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>
build.gradle
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:

URLWhat you get
http://localhost:8080/swagger-ui.htmlShort address that redirects to Swagger UI
http://localhost:8080/swagger-ui/index.htmlThe Swagger UI page
http://localhost:8080/v3/api-docsThe OpenAPI specification as JSON
http://localhost:8080/v3/api-docs.yamlThe 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:

UserRequest.java
package com.example.users;

public record UserRequest(String name, String email) {
}
UserResponse.java
package com.example.users;

public record UserResponse(long id, String name, String email) {
}
UserController.java
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.

AnnotationUsed onPurpose
@TagController classGroups endpoints under a name and description
@OperationMethodSummary and description of an endpoint
@ParameterMethod parameterDescribes a path, query or header parameter
@ApiResponse / @ApiResponsesMethodDocuments possible status codes and their bodies
@SchemaClass or fieldDescribes a model or one of its fields
@HiddenClass or methodHides an endpoint from the documentation
UserController.java (with annotations)
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:

UserRequest.java
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) {
}
UserResponse.java
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:

OpenApiConfig.java
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:

application.yml
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
PropertyPurpose
springdoc.api-docs.pathChanges the address of the JSON specification
springdoc.swagger-ui.pathChanges the address of Swagger UI
springdoc.packages-to-scanLimits documentation to certain packages
springdoc.paths-to-matchLimits documentation to certain URL patterns
springdoc.show-actuatorIncludes Actuator endpoints in the documentation
springdoc.api-docs.enabled / springdoc.swagger-ui.enabledTurns 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:

ApiGroupsConfig.java
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.

OpenApiSecurityConfig.java
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 {
}
Applying the scheme to a controller
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:

Documenting an error body
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

application-prod.yml
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

SecurityConfig.java
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.

Save the specification to a file
# 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

ProblemLikely causeFix
Swagger UI returns 404Wrong artifact for your Spring Boot version, or a context pathUse the matching springdoc version from Step 1; add the context path to the URL
Swagger UI shows a login page or 401Spring Security blocks the documentation pathsAllow /v3/api-docs/**, /swagger-ui/** and /swagger-ui.html
Some endpoints are missingpackages-to-scan or paths-to-match is too narrow, or @Hidden is usedCheck the springdoc properties and annotations
Request body shows as a plain string or objectEndpoint uses String, Map or ObjectUse a typed record or class for the body
Application fails to start after adding springdocspringdoc version does not match the Spring Boot versionUpgrade or downgrade springdoc to the matching line
Try it out calls the wrong hostApplication runs behind a proxySet the correct server URL in the OpenAPI bean or configure forwarded headers

19. Best Practices for Using Swagger in Spring Boot

DoAvoid
Add meaningful summaries and descriptions to every endpointLeaving endpoints with only their method names
Document error responses clearlyDocumenting only the 200 response
Use examples for request and response bodiesEmpty example values that hide the real format
Use typed request and response classesMap or String bodies that produce vague documentation
Disable or secure Swagger in productionLeaving Swagger UI open to the internet
Version your API and keep the documentation in syncHand-written documents that drift from the code
Use the springdoc version that matches Spring BootMixing incompatible versions
Export the specification and review changesBreaking 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.