DEV 8 min

OpenAPI in Spring Boot 3: API Docs & Production Hardening

Learn how to integrate and configure OpenAPI 3 with Springdoc in Spring Boot 3+ applications. Generate API contracts, secure endpoints in production, and enable Bearer JWT auth in Swagger UI.

Written by Samir El Azem Crovetto • Senior Backend Engineer & DevSecOps

The OpenAPI 3 Duality: Developer Velocity vs. Information Disclosure

In modern microservices architecture, the OpenAPI 3 specification (formerly Swagger) serves as the industry standard for documenting Application Programming Interface (API) contracts. Libraries such as springdoc-openapi automate the generation of interactive JSON schemas and web-based exploratory user interfaces (Swagger UI), enabling engineering teams to inspect endpoints, generate typed client SDKs, and execute automated integration tests seamlessly.

However, within enterprise production environments, publicly exposing interactive API documentation constitutes a critical Information Disclosure vulnerability. During the reconnaissance phase of penetration tests or targeted cyber attacks, threat actors scan for common routes like /swagger-ui/index.html or /v3/api-docs.

Unrestricted access to API documentation reveals:

  • A comprehensive topological map of endpoints, including unreleased beta routes and internal administrative paths.
  • Granular data models detailing field names, data types, validation constraints, and database relationships.
  • Hidden parameter keys susceptible to Mass Assignment and object manipulation attacks.
  • Framework, runtime, and server version identifiers that facilitate known CVE targeting.

Integrating OpenAPI into Spring Boot 3 requires a disciplined balance: maximizing engineering productivity during development cycles while enforcing perimeter isolation in production environments.


Access Control Architecture for OpenAPI and Swagger UI

To prevent external threat actors from leveraging interactive documentation, access to /v3/api-docs and /swagger-ui/** must be segregated at the perimeter API Gateway and the Spring Security filter chain:

graph TD
    subgraph External_Traffic ["🌐 External Ingress (Public Internet)"]
        PublicClient["Public Client / Mobile / Threat Actor"]
    end

    subgraph Internal_Traffic ["🏢 Internal Corporate Network"]
        DevOperator["Engineer / SRE / Auditor (VPN)"]
    end

    subgraph API_Gateway ["🛡️ API Gateway / Reverse Proxy"]
        PathRouter{"Path Routing Engine"}
    end

    subgraph Spring_Boot_Service ["⚙️ Spring Boot 3 Microservice"]
        SecurityFilterChain["Spring Security Filter Chain"]
        
        subgraph Endpoints ["Controllers"]
            BusinessAPI["Business APIs: /api/v1/orders"]
            SwaggerDocs["Documentation: /v3/api-docs & /swagger-ui"]
        end
    end

    PublicClient -->|HTTP Request| API_Gateway
    Internal_Traffic -->|Encrypted VPN Channel| API_Gateway
    API_Gateway --> PathRouter

    PathRouter -->|Route: /api/v1/**| SecurityFilterChain
    PathRouter -->|Route: /v3/api-docs from Internet| BlockGateway["HTTP 404 / 403 (Perimeter Block)"]
    PathRouter -->|Route: /v3/api-docs from VPN| SecurityFilterChain

    SecurityFilterChain -->|Valid JWT Bearer| BusinessAPI
    SecurityFilterChain -->|Authority: 'ROLE_INTERNAL_DEV'| SwaggerDocs
    SecurityFilterChain -.->|Insufficient Scope| Deny["HTTP 401 / 403"]

    classDef public fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
    classDef internal fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
    classDef edge fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
    classDef block fill:#ffebee,stroke:#d32f2f,stroke-width:2px;

    class PublicClient public;
    class DevOperator internal;
    class API_Gateway,PathRouter edge;
    class BlockGateway,Deny block;
    class SecurityFilterChain,BusinessAPI,SwaggerDocs internal;

1. Integrating springdoc-openapi in Spring Boot 3 (Java 21)

In Spring Boot 3 (leveraging Spring Framework 6 and Jakarta EE 10), official integration is provided via the modular springdoc-openapi-starter-webmvc-ui dependency.

Build Configuration in pom.xml:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.5.0</version>
</dependency>

Application Baseline in application.yml:

Rather than using predictable default URLs, customize path prefixes and disable interactive UI interfaces in production deployment profiles:

# Local development profile
springdoc:
  api-docs:
    path: /internal/docs/v3/api-docs
    enabled: true
  swagger-ui:
    path: /internal/docs/swagger-ui.html
    enabled: true
    operationsSorter: method
    tagsSorter: alpha
    display-request-duration: true

---
# Production profile (application-prod.yml)
spring:
  config:
    activate:
      on-profile: prod

springdoc:
  # Completely disable Swagger UI in public production environments
  swagger-ui:
    enabled: false
  # Disable raw schema generation on public surfaces
  api-docs:
    enabled: false

2. Programmatic Configuration and JWT Bearer (OAuth2 / OIDC) Support

To enable authenticated testing directly within Swagger UI for protected endpoints, declare an HTTP Bearer SecurityScheme within the OpenAPI bean configuration:

package es.secdevoops.config;

import io.swagger.v3.oas.models.Components;
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.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class OpenApiSecurityConfig {

    private static final String SECURITY_SCHEME_NAME = "BearerAuthentication";

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("Corporate Microservices API")
                .description("Enterprise RESTful API with JWT authentication and RBAC controls.")
                .version("1.4.0")
                .contact(new Contact()
                    .name("Cybersecurity Architecture Team")
                    .email("security@enterprise.local"))
                .license(new License().name("Proprietary")))
            .servers(List.of(
                new Server().url("https://api.enterprise.local").description("Production Environment"),
                new Server().url("http://localhost:8080").description("Local Testing Environment")
            ))
            // Global security requirement across documented endpoints
            .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
            .components(new Components()
                .addSecuritySchemes(SECURITY_SCHEME_NAME, new SecurityScheme()
                    .name(SECURITY_SCHEME_NAME)
                    .type(SecurityScheme.Type.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")
                    .description("Enter your JWT token with 'Bearer ' prefix")));
    }
}

3. Data Model Hardening: Preventing Internal Schema Exposure

A severe vulnerability pattern is generating OpenAPI schemas directly from underlying persistence entities (JPA entities).

The JPA Direct Exposure Antipattern:

Exposing a database entity like UserEntity in a REST controller results in Swagger serializing fields such as passwordHash, failedLoginAttempts, or tenantId, leaking internal database topologies.

Production Solution: Immutable Java 21 Records and @Schema(hidden = true)

Application controllers should operate strictly over Data Transfer Objects (DTOs), annotated with OpenAPI constraints to conceal sensitive metadata:

package es.secdevoops.dto;

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 = "User account registration request payload")
public record CreateUserRequest(
    @Schema(description = "Unique user account handle", example = "samir.dev", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank @Size(min = 3, max = 50)
    String username,

    @Schema(description = "Corporate email address", example = "samir@enterprise.local")
    @NotBlank @Email
    String email,

    @Schema(description = "Account plaintext password", accessMode = Schema.AccessMode.WRITE_ONLY)
    @NotBlank @Size(min = 12)
    String password,

    @Schema(hidden = true) // Hide internal telemetry fields from generated documentation
    String internalAuditTrackingId
) {}
  • accessMode = Schema.AccessMode.WRITE_ONLY: Dictates that the property is strictly accepted in inbound payloads (POST/PUT), preventing inclusion in outbound response schemas.
  • @Schema(hidden = true): Excises the field entirely from generated contract specifications.

4. Securing Documentation Endpoints in Spring Security

When an organization requires accessible Swagger documentation in non-production environments or across internal developer networks, endpoints must be guarded via Spring Security:

package es.secdevoops.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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // Disabled for stateless JWT APIs
            .authorizeHttpRequests(auth -> auth
                // Public business endpoints
                .requestMatchers("/api/v1/auth/**", "/actuator/health").permitAll()
                // Restrict Swagger UI and OpenAPI JSON to authenticated developer roles
                .requestMatchers(
                    "/internal/docs/**",
                    "/swagger-ui/**",
                    "/v3/api-docs/**"
                ).hasAuthority("SCOPE_internal:read_docs")
                // All other business endpoints require standard authentication
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));

        return http.build();
    }
}

Any unauthenticated attempt to query API documentation routes triggers an immediate HTTP 401 Unauthorized response, blocking automated reconnaissance scanners.


5. API Group Segregation: GroupedOpenApi and Selective Documentation

In systems exposing both external public-facing APIs for partner integration and sensitive internal APIs for administrative operations, a monolithic OpenAPI specification inadvertently leaks private operational endpoints.

Springdoc addresses this cleanly via GroupedOpenApi beans, publishing isolated contracts along distinct URI pathways:

package es.secdevoops.config;

import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApiGroupingConfig {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
            .group("public-v1")
            .pathsToMatch("/api/v1/public/**")
            .build();
    }

    @Bean
    public GroupedOpenApi internalAdminApi() {
        return GroupedOpenApi.builder()
            .group("internal-admin")
            .pathsToMatch("/api/v1/admin/**", "/actuator/**")
            .build();
    }
}

By coupling grouped endpoints with edge routing rules, public gateways selectively expose only /v3/api-docs/public-v1 to external clients while completely hiding /v3/api-docs/internal-admin.


6. Contract Verification and CI/CD Security Linting with Spectral

API documentation must not only be managed at runtime; in mature DevSecOps deployment pipelines, OpenAPI specifications should undergo static analysis during automated CI/CD builds using schema linters such as Spectral (by Stoplight):

# Export generated OpenAPI specification and run automated security and style linting
npx @stoplight/spectral-cli lint openapi.json --ruleset spectral:oas

Spectral automated rulesets enforce critical quality gates:

  • Flagging operations that fail to define error response status codes (400, 401, 403, 500).
  • Enforcing explicit parameter formats and boundary validations.
  • Detecting unconstrained payloads lacking size limits or strict format masks.
  • Ensuring every state-modifying mutating endpoint declares explicit authentication schemes.

7. Perimeter Enforcement: Gateway Filtering

Within microservice topologies fronted by an API Gateway (Spring Cloud Gateway, Envoy, Kong, or AWS API Gateway), enforce edge filtering rules to strip documentation paths entirely from public routing:

# Spring Cloud Gateway configuration
spring:
  cloud:
    gateway:
      routes:
        - id: backend-service-public
          uri: lb://backend-service
          predicates:
            - Path=/api/v1/**
          filters:
            # Strip fingerprinting response headers
            - RemoveResponseHeader=X-Powered-By
            - RemoveResponseHeader=Server

        # Actively block external attempts to access OpenAPI endpoints
        - id: block-swagger-docs
          uri: noop://
          predicates:
            - Path=/**/api-docs/**, /**/swagger-ui/**
          filters:
            - SetStatus=404

Enterprise OpenAPI Hardening Checklist

  • [x] Integrated springdoc-openapi-starter-webmvc-ui on modern Spring Boot 3 standards.
  • [x] Customized default documentation paths to prevent automated endpoint reconnaissance.
  • [x] Swagger UI completely disabled in production profiles via springdoc.swagger-ui.enabled: false.
  • [x] Registered JWT Bearer security schemes for authenticated testing in developer environments.
  • [x] Decoupled JPA entities from API contracts using immutable DTO records and @Schema(hidden = true).
  • [x] Documentation endpoints filtered at the API Gateway perimeter to block public discovery.

If you need to audit your organization's Spring Boot REST APIs, remediate information disclosure risks, or architect secure microservices with OAuth2 and JWT, explore my Cybersecurity Consulting Services or reach out directly via About Me.