OpenAPITools / OpenAPITools/openapi-generator

[BUG] Cross-module schema contamination with mvnd (Maven Daemon) parallel builds since swagger-parser 2.1.24

Open
#23,321 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Title: [BUG] Cross-module schema contamination with mvnd (Maven Daemon) parallel builds since swagger-parser 2.1.24

Description

When using openapi-generator-maven-plugin with mvnd (Maven Daemon) and parallel builds (-T1C), schemas from one module contaminate another module's generated code. The bug manifests when two independent modules define schemas with case-near-colliding names (e.g. CountryDto vs CountryDTO).

What happened

Module A defines CountryDto with fields identifier, label.
Module B defines CountryDTO with fields code, name.

After several builds with a warm mvnd daemon, module A's generated CountryDto.java sometimes contains:

  • File named CountryDto.java but class is CountryDTO
  • Fields code, name (from module B's schema) instead of identifier, label
  • Correct package (module A's package), wrong class content (module B's schema)

The issue reproduces almost every time with a warm daemon but never on the first build after daemon restart, and never with mvnd clean test-compile (only mvnd clean compile).

What was expected

Each module generates code exclusively from its own OpenAPI spec, regardless of parallel execution or daemon state.

Root cause analysis

The issue was introduced in swagger-parser 2.1.24 and affects all openapi-generator versions that bundle swagger-parser ≥2.1.24 (i.e. openapi-generator 7.14.0+).

swagger-parser 2.1.24 introduced two changes:

  1. ResolverFully response resolution (swagger-parser#2131) — adds response resolution logic that mutates shared state
  2. IdsTraverser for OAS 3.1 $id dereferencing — new 675-line traverser that populates DereferencerContext.idsCache via Json31.pretty(schema)

These changes introduce state that leaks across builds in mvnd's long-lived daemon JVM. When two modules generate code in parallel, schemas from one module's execution contaminate the other.

This is NOT the same issue as swagger-core#4672 (Json.mapper() thread-safety). That was fixed in swagger-core 2.2.24, and both working and broken swagger-parser versions use swagger-core ≥2.2.24 with that fix included.

Version matrix
swagger-parser swagger-core (plugin classpath) Warm mvnd parallel build
2.1.22 2.2.22 ✅ Works
2.1.23 2.2.23 Works (last good)
2.1.24 2.2.24 Breaks (first bad)
2.1.25–2.1.27 2.2.28–2.2.29 ❌ Breaks
2.1.28 2.2.32 ❌ Breaks
2.1.37 2.2.37 ❌ Breaks
Why mvnd specifically

Unlike standard Maven, mvnd keeps a long-lived JVM daemon process alive between builds. Static state in plugin classloaders persists across builds:

  • StringUtils Caffeine caches (camelized/underscored/escaped words)
  • DefaultCodegen.sanitizedNameCache
  • Json.mapper() / Json31.mapper() ObjectMapper singletons
  • Any state accumulated in ResolverFully, ResolverCache, or IdsTraverser

The race condition requires:

  1. Parallel module execution (-T1C) — two independent modules run code generation simultaneously
  2. Warm daemon — the JVM has accumulated state from prior builds
  3. Case-near-colliding schema names — e.g. CountryDto vs CountryDTO across modules

Standard Maven (mvn) gets a fresh JVM per invocation, so the issue does not reproduce there.

Reproduction environment
  • openapi-generator-maven-plugin: 7.20.0 (also tested with 7.14.0, 7.15.0)
  • mvnd: 1.0.5
  • Java: 21 (Temurin 21.0.10+7)
  • OS: macOS (Darwin 25.3.0)
  • Build config: -T1C in .mvn/maven.config

Project structure:

  • Module A: 2 executions (v1 + v2 specs), defines CountryDto (fields: identifier, label)
  • Module B: 55 executions (various third-party specs), defines CountryDTO (fields: code, name)
  • Modules A and B have no Maven dependency between them → run in parallel

How to reproduce:

# With mvnd and -T1C configured:
mvnd clean compile          # warm up daemon
mvnd clean compile          # repeat 3-5 times
# Check module A's generated CountryDto.java — it will contain CountryDTO with wrong fields

Notable: mvnd clean test-compile does not trigger the bug, only mvnd clean compile. The additional phases in test-compile likely give the Caffeine caches time to expire (they use expireAfterAccess(5, SECONDS) in StringUtils and expireAfterAccess(10, SECONDS) in DefaultCodegen), which prevents the stale state from leaking into the next build.

A standalone minimal reproduction project has not been created yet, as the race condition is timing-sensitive and requires sufficient parallel work to trigger.

Workaround

Pin swagger-parser to 2.1.23 in the plugin's dependency override. Version 2.1.23 has the ParseOptions.setResolveResponses() API (needed by openapi-generator 7.15.0+) but predates the buggy ResolverFully implementation:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.20.0</version>
    <dependencies>
        <dependency>
            <groupId>io.swagger.parser.v3</groupId>
            <artifactId>swagger-parser</artifactId>
            <version>2.1.23</version>
        </dependency>
    </dependencies>
</plugin>
Related issues
openapi-generator version

7.20.0 (also affects 7.14.0+)

OpenAPI declaration file content or url

N/A (requires two separate specs with case-near-colliding schema names)

Generation Details
generatorName: spring
library: spring-boot
configOptions:
  useSpringBoot3: true
  interfaceOnly: true
  openApiNullable: false
Steps to reproduce

See "How to reproduce" section above.

Related issues/PRs

See "Related issues" section above.

Suggest a fix

The openapi-generator-maven-plugin should either:

  1. Ensure all static caches are cleared between executions (especially in mvnd context)
  2. Use instance-level caches instead of static caches
  3. Scope shared state per-execution or per-module rather than per-JVM

The immediate fix is in swagger-parser: the ResolverFully and IdsTraverser changes in 2.1.24 need to ensure no mutable state leaks across independent parse operations.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the warm mvnd reproduction using parallel clean compile, then compare swagger-parser 2.1.23 and 2.1.24. Read the referenced ResolverFully and IdsTraverser changes, along with the related swagger-parser#2295 issue, to identify state that crosses parse operations. Done means independent modules generate only their own schemas across repeated daemon builds, with a regression test or minimal reproduction.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
build-system, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.