-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
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.javabut class isCountryDTO - Fields
code,name(from module B's schema) instead ofidentifier,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:
ResolverFullyresponse resolution (swagger-parser#2131) — adds response resolution logic that mutates shared stateIdsTraverserfor OAS 3.1$iddereferencing — new 675-line traverser that populatesDereferencerContext.idsCacheviaJson31.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:
StringUtilsCaffeine caches (camelized/underscored/escaped words)DefaultCodegen.sanitizedNameCacheJson.mapper()/Json31.mapper()ObjectMapper singletons- Any state accumulated in
ResolverFully,ResolverCache, orIdsTraverser
The race condition requires:
- Parallel module execution (
-T1C) — two independent modules run code generation simultaneously - Warm daemon — the JVM has accumulated state from prior builds
- Case-near-colliding schema names — e.g.
CountryDtovsCountryDTOacross 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:
-T1Cin.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 fieldsNotable: 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
- swagger-parser#2295 — primary issue filed on swagger-parser (state leaks since 2.1.24)
- swagger-core#4672 —
Json.mapper()thread-safety (fixed, but different issue) - openapi-generator#1597 — earlier race condition report
- swagger-codegen#6260 — thread-safety in swagger-codegen
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:
- Ensure all static caches are cleared between executions (especially in mvnd context)
- Use instance-level caches instead of static caches
- 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.