This Was Not Just A Swagger UI Problem

Springfox can fail before Swagger UI has a chance to render. The useful debugging boundary is the request-mapping metadata that Springfox scans while the application starts, not the browser page that never loads.

The runnable lab in labs/swagger-springfox-migration-lab/ exercises the same tiny /orders/{orderId} API through three cases:

  • Spring Boot 2.7.18 + Springfox 3.0.0 + PathPatternParser.
  • Spring Boot 2.7.18 + Springfox 3.0.0 + AntPathMatcher plus a handler-mapping filter.
  • Spring Boot 3.4.7 + springdoc-openapi-starter-webmvc-ui 2.8.17, grouped docs, and bearer-token OpenAPI metadata.

The goal is not to argue that one documentation library is always better. The goal is to make a version-boundary failure observable, then choose the smallest responsible fix for each application generation.

The Failure

The failure looked like a Swagger parse error, but the important line was lower in the stack trace:

Failed to start bean 'documentationPluginsBootstrapper';
nested exception is java.lang.NullPointerException:
Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()"
because "this.condition" is null

at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns
at springfox.documentation.RequestHandler.sortedPaths
at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers

That tells us Springfox is not merely failing to render UI resources. It is failing while scanning Spring MVC request mappings and sorting discovered paths before the docs endpoint can be served.

The reproduced evidence is saved in /resources/blog/post-50-swagger/springfox-default-path-pattern-failure.txt.

Why Path Matching Matters

Spring MVC has two path-matching models that matter here:

ModelRough shapeWhy it matters for Springfox
AntPathMatcherString-pattern based matchingOlder libraries often expect PatternsRequestCondition to be populated
PathPatternParserParsed path-pattern matchingRequest mapping metadata can be represented without the old condition object Springfox expects

Spring Framework’s path matching documentation describes PathPatternParser as the parsed-pattern alternative to AntPathMatcher. Spring Boot’s 2.7 web documentation explains the matching-strategy property. A Spring Boot 2.6 documentation issue also records that the MVC auto-configuration default was actually path-pattern-parser in that line.

Springfox 3.0.0 was built around older Spring MVC assumptions. Its own Spring Boot application notes mainly describe dependency and annotation changes for Springfox 3.x. They do not turn Springfox into a modern Spring Boot 3 documentation stack.

That is why “Swagger broke” is too vague. The actual bug is:

Springfox’s request-handler scanner expects mapping metadata that is not available in the same shape when Spring MVC uses parsed path patterns.

Lab Design

The lab uses Maven inside a JDK 17 Docker container:

services:
  maven:
    image: maven:3.9.9-eclipse-temurin-17
    working_dir: /workspace
    volumes:
      - .:/workspace
      - maven-cache:/root/.m2

There are two modules:

springfox-boot27
springdoc-boot3

Both expose the same API:

@RestController
class OrderController {
    @GetMapping("/orders/{orderId}")
    OrderResponse getOrder(@PathVariable String orderId) {
        return new OrderResponse(orderId, "PAID", 42000);
    }
}

Run it:

cd labs/swagger-springfox-migration-lab
./scripts/run-matrix.sh

The successful rerun used ID 20260710T024421Z.

Observed Matrix

The clean run produced this result:

ScenarioEndpointObserved outcome
Boot 2.7.18 + Springfox 3.0.0 + PathPatternParser/v2/api-docsapplication startup failed before the endpoint could be served
Boot 2.7.18 + Springfox 3.0.0 + AntPathMatcher + handler-mapping filter/v2/api-docsreturned a Swagger 2 document
Boot 3.4.7 + springdoc-openapi 2.8.17/v3/api-docs and /v3/api-docs/ordersreturned OpenAPI 3.1 documents with bearer auth metadata

The generated summary is saved at /resources/blog/post-50-swagger/matrix-summary.md.

The positive Springfox case returned:

Scenario: Spring Boot 2.7.18 + Springfox 3.0.0 + AntPathMatcher + handler-mapping filter
Outcome: /v2/api-docs returned a Swagger 2 document
Contains swagger 2 marker: true
Contains /orders/{orderId}: true
Contains OrderResponse schema: true

The springdoc migration case returned:

Scenario: Spring Boot 3.4.7 + springdoc-openapi-starter-webmvc-ui 2.8.17
Outcome: /v3/api-docs returned an OpenAPI document
HTTP status: 200 OK
Contains openapi marker: true
Contains /orders/{orderId}: true
Contains OrderResponse schema: true
Contains bearerAuth security scheme: true
Grouped endpoint /v3/api-docs/orders status: 200 OK
Unauthenticated GET /orders/demo-1 status: 403 FORBIDDEN

Those two lines are more important than a Swagger UI screenshot. They prove that the generated machine-readable contract exists and includes the expected operation and schema.

Boot 2.7: If You Cannot Migrate Immediately

For an existing Spring Boot 2.7 service that still uses Springfox, the compatibility path in the lab is explicit:

spring.mvc.pathmatch.matching-strategy=ant-path-matcher
lab.springfox.compatibility-filter=true

The handler-mapping filter is intentionally isolated behind a property:

@Configuration
@ConditionalOnProperty(name = "lab.springfox.compatibility-filter", havingValue = "true")
class SpringfoxCompatibilityConfig {
    @Bean
    static BeanPostProcessor springfoxHandlerMappingFilter() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) {
                if ("springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider"
                    .equals(bean.getClass().getName())) {
                    filterParsedPatternMappings(bean);
                }
                return bean;
            }
        };
    }
}

The real implementation in the lab reflects into Springfox’s handlerMappings list and removes mappings whose getPatternParser() is not null. This is a compatibility patch, not a clean architecture pattern. It depends on Springfox internals and should be covered by an integration test that actually calls /v2/api-docs.

My rule after reproducing this is:

  • If the application is staying on Boot 2 for a while, keep the workaround narrow and tested.
  • If the application is moving to Boot 3, do not carry Springfox forward.

Boot 3: Migrate The Documentation Stack

Spring Boot 3 moved to Jakarta namespaces and a newer Spring Framework baseline. Treating Springfox as something to patch through that migration is the wrong default.

The springdoc path in the lab uses:

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

The migrated module also adds spring-boot-starter-security, a SecurityFilterChain, a grouped OpenAPI bean, and a bearer-token security scheme:

@Configuration
@SecurityScheme(
    name = "bearerAuth",
    type = SecuritySchemeType.HTTP,
    scheme = "bearer",
    bearerFormat = "JWT"
)
class OpenApiConfig {
    @Bean
    GroupedOpenApi ordersApi() {
        return GroupedOpenApi.builder()
            .group("orders")
            .pathsToMatch("/orders/**")
            .build();
    }
}

The security filter keeps generated docs reachable while protecting the API endpoint:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
            .anyRequest().authenticated()
        )
        .build();
}

The springdoc documentation says this starter exposes Swagger UI and the OpenAPI JSON endpoint for Spring Boot applications. In the lab, /v3/api-docs returned:

{
  "openapi": "3.1.0",
  "paths": {
    "/orders/{orderId}": {
      "get": {
        "summary": "Find one order"
      }
    }
  }
}

The important migration difference is not just endpoint naming:

Springfox pathspringdoc path
/v2/api-docs/v3/api-docs
Swagger 2 model by defaultOpenAPI 3.x model
Docket configurationstarter auto-configuration plus @Operation, @Tag, GroupedOpenApi
Boot 2 compatibility workaround may be neededBoot 3-compatible library line
Ad hoc security snippetsexplicit docs allowlist plus OpenAPI bearerAuth scheme

If the frontend, QA tooling, or API gateway still expects /v2/api-docs, the migration must include that consumer contract. Do not silently change the docs endpoint and call the backend migration done.

Security Configuration Is A Separate Test

Swagger endpoints also need an explicit Spring Security decision. A Boot 2-era allowlist often looks like this:

.antMatchers("/swagger-ui/**", "/v3/api-docs", "/v2/api-docs").permitAll()

That line is version-sensitive:

  • Boot 2 / Spring Security 5 commonly used antMatchers.
  • Boot 3 / Spring Security 6 uses the newer authorizeHttpRequests style and requestMatchers.

More importantly, a docs endpoint should not automatically be public in every environment. For internal admin APIs, it may be better to expose docs only behind VPN, SSO, basic auth, or a non-production profile.

The lab now turns that into a regression test:

GET /v3/api-docs as an unauthenticated user
expected: 200 OK

GET /orders/demo-1 as an unauthenticated user
expected: 403 FORBIDDEN

That test tells you more than a copied allowlist snippet.

Debugging Checklist

When Swagger/OpenAPI generation breaks after a Spring Boot upgrade, check it in this order:

  1. Identify the docs library and version.

    Springfox, springdoc, and raw Swagger UI are not interchangeable. The failure mode depends on which library is scanning Spring MVC mappings.

  2. Find the endpoint that should exist.

    For Springfox it is usually /v2/api-docs. For springdoc it is usually /v3/api-docs. Test the JSON endpoint before debugging the UI.

  3. Read the stack trace at the scanner layer.

    If the stack contains documentationPluginsBootstrapper, WebMvcRequestHandlerProvider, or WebMvcPatternsRequestConditionWrapper, suspect Springfox and MVC path matching.

  4. Check Spring MVC path matching.

    If you are on Boot 2.6 or 2.7 and still using Springfox, compare path-pattern-parser and ant-path-matcher with an integration test.

  5. Separate compatibility from migration.

    A Boot 2 compatibility patch may be acceptable as a temporary hold. It should not become the Boot 3 migration strategy.

  6. Verify security and deployment behavior.

    The JSON docs endpoint, the Swagger UI static assets, reverse-proxy path rewriting, and auth filters can fail independently.

What This Proves In An Interview

This lab is small, but it demonstrates a better debugging habit than “I followed a Swagger setup tutorial.”

It proves that I can:

  • Reproduce a framework compatibility failure with a minimal app.
  • Turn a stack trace into a hypothesis about request-mapping internals.
  • Separate UI rendering from generated API contract availability.
  • Keep a risky workaround contained and tested.
  • Define a migration path that accounts for consumers of /v2/api-docs and /v3/api-docs.

The practical conclusion is direct:

For existing Boot 2.7 services, patch Springfox only as a tested bridge. For Boot 3 services, migrate to springdoc and verify /v3/api-docs as part of the upgrade.

That is the engineering decision hidden behind what first looked like a Swagger parse error.