Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1b9a973
feat(normalizer): enhance custom normalizer class loading with contex…
Picazsoo Aug 5, 2026
d16cf1b
fix: address review feedback on NORMALIZER_CLASS classloader fix
Picazsoo Aug 5, 2026
4c11967
docs: document NORMALIZER_CLASS classpath requirement on openapiNorma…
Picazsoo Aug 5, 2026
df8885b
fix: address review feedback on normalizer classloading and tests
Picazsoo Aug 5, 2026
81f199f
fix: guard runtime compiler use and strengthen positive-path assertions
Picazsoo Aug 5, 2026
daf608b
test: verify NORMALIZER_CLASS actually executed via marker file
Picazsoo Aug 5, 2026
bea8a77
test: guard ToolProvider.getSystemJavaCompiler() null case in Kotlin …
Picazsoo Aug 5, 2026
3f0d4b9
fix: enhance CodegenConfigLoader to use context class loader and impr…
Picazsoo Aug 5, 2026
41ec7f7
improve test coverage
Picazsoo Aug 5, 2026
dd5d274
fix: update compileCodegenFixture to accept generator name parameter
Picazsoo Aug 5, 2026
1696a23
fix: enhance CodegenConfigLoader to improve class loading and error h…
Picazsoo Aug 5, 2026
ee001be
fix: enhance CodegenConfigLoader to provide detailed error handling f…
Picazsoo Aug 5, 2026
c20d2d5
fix: enhance CodegenConfigLoader to improve error handling for class …
Picazsoo Aug 5, 2026
820f0ff
fix test
Picazsoo Aug 6, 2026
688cf4b
fix: enhance CodegenConfigLoader to improve handling of initializatio…
Picazsoo Aug 6, 2026
c76fd40
implement CR suggestion
Picazsoo Aug 6, 2026
cb631e6
fix: enhance CodegenConfigLoader to improve error handling for servic…
Picazsoo Aug 7, 2026
acb402e
implement fix for the CR feedback
Picazsoo Aug 7, 2026
e0f00a8
fix: simplify initialization failures handling in CodegenConfigLoader
Picazsoo Aug 7, 2026
4957152
Merge branch 'master' into feature/NORMALIZER-CLASS-in-process
Picazsoo Aug 7, 2026
b902475
update samples
Picazsoo Aug 7, 2026
4219323
fix CR suggestion
Picazsoo Aug 7, 2026
f8835ef
fix CR suggestion
Picazsoo Aug 7, 2026
04674ae
implement changes from CR feedback
Picazsoo Aug 7, 2026
ce9ee8e
implement changes from CR feedback
Picazsoo Aug 7, 2026
920f6a7
Revert "implement changes from CR feedback"
Picazsoo Aug 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,18 @@ Example:
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/required-properties.yaml -o /tmp/java-okhttp/ --openapi-normalizer NORMALIZER_CLASS=org.openapitools.codegen.OpenAPINormalizerTest$RemoveRequiredNormalizer
```

The class must be resolvable on the generation runtime classpath. When using the
[Gradle plugin](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin),
a custom `NORMALIZER_CLASS` that isn't already on the plugin's own classpath must be added via the
`openApiGeneratorExtra` dependency configuration (or the `generatorClasspath` property) so it is forwarded to the
code generation worker in both `workerIsolation` modes (`process` and `classloader`) - see the plugin's README for
details.

Similarly, a custom generator selected by name or fully qualified class name must be resolvable on the generation
runtime classpath. With the Gradle plugin, add its jar or project output to `openApiGeneratorExtra` (preferred) or
`generatorClasspath` so it is forwarded to the code generation worker in both `process` and `classloader`
`workerIsolation` modes.

- `LOOSE_NULL_DEFINITIONS`: When set to true, allow more schema definitions in OpenAPI 3.0 spec to be the same as `null` in OpenAPI 3.1 spec by setting ModelUtils.looseNullDefinitions to true.

Example:
Expand Down
48 changes: 48 additions & 0 deletions modules/openapi-generator-gradle-plugin/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,56 @@ warning].
|Gradle default (~512 MiB)
|Maximum heap size for the forked worker JVM when `workerIsolation` is `process` (e.g. `"512m"`, `"1g"`).
Has no effect when `workerIsolation` is `classloader`.

|generatorClasspath
|ConfigurableFileCollection
|(empty)
a|Additional classpath entries (jars, class directories, project outputs) forwarded to the code generation worker
in *both* `workerIsolation` modes (`process` and `classloader`). Required for any custom class referenced by name
in generator options - most notably a custom `NORMALIZER_CLASS` (see `openapiNormalizer`) or custom generator
selected by `generatorName`/FQCN - to be resolvable by the worker, since such classes are not on the plugin's own
runtime classpath.

For dependencies from a repository or another project, prefer adding them to the `openApiGeneratorExtra`
configuration created by this plugin (see below); `generatorClasspath` is a lower-level escape hatch for ad hoc
files/directories:

[source,groovy]
----
openApiGenerate {
generatorClasspath.from(files("libs/my-normalizer.jar", "libs/my-custom-generator.jar"))
}
----
|===

[NOTE]
====
The plugin creates an `openApiGeneratorExtra` dependency configuration (resolvable, not published) whose entries
are automatically forwarded into `generatorClasspath` for both `workerIsolation` modes. Use it to declare a
custom `NORMALIZER_CLASS`, custom generator selected by `generatorName`/FQCN, or any other class referenced by name
in generator options as a normal Gradle dependency - a published artifact, a local jar, or another project in the
same build:

[source,groovy]
----
dependencies {
openApiGeneratorExtra("com.acme:my-normalizer:1.0.0") // custom NORMALIZER_CLASS
openApiGeneratorExtra("com.acme:my-custom-generator:1.0.0") // custom generator
openApiGeneratorExtra(project(":my-generator-module")) // project dependency; built automatically
openApiGeneratorExtra(files("libs/my-normalizer.jar")) // local normalizer jar/class directory
openApiGeneratorExtra(files("libs/my-custom-generator.jar")) // local generator jar/class directory
}

openApiGenerate {
generatorName = "com.acme.MyGenerator"
openapiNormalizer = ["NORMALIZER_CLASS": "com.acme.MyNormalizer"]
}
----

Without a corresponding entry in `openApiGeneratorExtra` or `generatorClasspath`, a custom `NORMALIZER_CLASS` or
custom generator selected by name/FQCN will fail to load with a clear error, regardless of `workerIsolation` mode.
====

[NOTE]
====
Configuring any one of `apiFilesConstrainedTo`, `modelFilesConstrainedTo`, or `supportingFilesConstrainedTo` results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ class OpenApiGeneratorPlugin : Plugin<Project> {

generate.outputDir.convention(layout.buildDirectory.dir("generate-resources/main"))

// A dependency configuration users can add custom classes to (e.g. a jar containing a
// custom NORMALIZER_CLASS) so they are forwarded to the code generation worker's
// classpath, in both "process" and "classloader" workerIsolation modes. Not consumed or
// published; only resolved by this plugin.
val generatorExtraClasspath = configurations.create("openApiGeneratorExtra") {
isVisible = false
isCanBeConsumed = false
isCanBeResolved = true
description = "Additional classpath entries (e.g. custom NORMALIZER_CLASS jars) " +
"forwarded to the openApiGenerate worker in both process and classloader isolation."
}

tasks.apply {
register("openApiGenerators", GeneratorsTask::class.java).configure {
group = pluginGroup
Expand Down Expand Up @@ -174,6 +186,8 @@ class OpenApiGeneratorPlugin : Plugin<Project> {
generateRecursiveDependentModels.set(generate.generateRecursiveDependentModels)
workerIsolation.set(generate.workerIsolation)
maxWorkerHeapSize.set(generate.maxWorkerHeapSize)
generatorClasspath.from(generatorExtraClasspath)
generatorClasspath.from(generate.generatorClasspath)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ open class OpenApiGeneratorGenerateExtension(private val project: Project) {
* Example rules: `REFACTOR_ALLOF_WITH_PROPERTIES_ONLY=true`,
* `REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY=true`. See the OpenAPI Generator docs for
* the full list of normalizer rules.
*
* For the `NORMALIZER_CLASS` rule (a custom class extending `OpenAPINormalizer`), the class
* must be added to the [generatorClasspath] (or the `openApiGeneratorExtra` dependency
* configuration) so it is resolvable by the code generation worker in both `workerIsolation`
* modes; otherwise it will fail to load with a `ClassNotFoundException`.
*/
val openapiNormalizer = project.objects.mapProperty<String, String>()

Expand Down Expand Up @@ -536,6 +541,33 @@ open class OpenApiGeneratorGenerateExtension(private val project: Project) {
*/
val maxWorkerHeapSize = project.objects.property<String>()

/**
* Additional classpath entries (jars, class directories, project outputs) made visible to the
* code generation worker in *both* [workerIsolation] modes (`process` and `classloader`).
*
* This is the mechanism by which custom classes referenced by name in generator options -
* most notably a custom `NORMALIZER_CLASS` in [openapiNormalizer] - are resolved: such classes
* are not on the plugin's own runtime classpath, so without contributing them here the worker
* (particularly under `process` isolation, which runs in an isolated JVM) cannot load them.
*
* For the common case of depending on a published artifact or another project's output, prefer
* adding a dependency to the `openApiGeneratorExtra` configuration created by this plugin, e.g.:
* ```kotlin
* dependencies {
* openApiGeneratorExtra("com.acme:my-normalizer:1.0.0")
* openApiGeneratorExtra(project(":my-normalizer-module"))
* }
* ```
* Entries resolved from `openApiGeneratorExtra` are always included automatically; this
* property is an additional, lower-level escape hatch for ad hoc files or directories, e.g.:
* ```kotlin
* openApiGenerate {
* generatorClasspath.from(files("libs/my-normalizer.jar"))
* }
* ```
*/
val generatorClasspath: ConfigurableFileCollection = project.objects.fileCollection()

init {
applyDefaults()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,17 @@ abstract class GenerateTask : DefaultTask() {
@get:Input
abstract val maxWorkerHeapSize: Property<String>

/**
* Additional classpath entries forwarded to the code generation worker in both `process` and
* `classloader` [workerIsolation] modes. Populated by default from the `openApiGeneratorExtra`
* configuration, plus any files/directories added via the `openApiGenerate` extension's
* `generatorClasspath` property. Required for custom classes referenced by name in generator
* options (e.g. a custom `NORMALIZER_CLASS`) to be resolvable by the worker.
*/
@get:Optional
@get:Classpath
abstract val generatorClasspath: ConfigurableFileCollection

/**
* The verbosity of generation
*/
Expand Down Expand Up @@ -693,6 +704,11 @@ abstract class GenerateTask : DefaultTask() {
* Example rules: `REFACTOR_ALLOF_WITH_PROPERTIES_ONLY=true`,
* `REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY=true`. See the OpenAPI Generator docs for
* the full list of normalizer rules.
*
* For the `NORMALIZER_CLASS` rule (a custom class extending `OpenAPINormalizer`), the class
* must be added to [generatorClasspath] (or the `openApiGeneratorExtra` dependency
* configuration) so it is resolvable by the code generation worker in both `workerIsolation`
* modes; otherwise it will fail to load with a `ClassNotFoundException`.
*/
@get:Optional
@get:Input
Expand Down Expand Up @@ -1148,6 +1164,7 @@ abstract class GenerateTask : DefaultTask() {
)
}
workerExecutor.processIsolation {
classpath.from(generatorClasspath)
maxWorkerHeapSize.orNull?.let { forkOptions.maxHeapSize = it }
}
}
Expand All @@ -1160,7 +1177,9 @@ abstract class GenerateTask : DefaultTask() {
"consider workerIsolation = \"process\" if you hit metaspace pressure)"
)
}
workerExecutor.classLoaderIsolation()
workerExecutor.classLoaderIsolation {
classpath.from(generatorClasspath)
}
}

else -> throw GradleException("Invalid workerIsolation mode: $isolation. Supported values are 'process' and 'classloader'.")
Expand Down
Loading
Loading