OpenAPITools/openapi-generator · error · RuntimeException

Failed to load custom NORMALIZER_CLASS '{className}'. This c

Error message

Failed to load custom NORMALIZER_CLASS '{className}'. This class must be visible on the generation runtime classpath (i.e. resolvable either by the current thread's context classloader or by the classloader that loaded openapi-generator itself). Ensure the class (and its dependencies) is on the classpath used to launch the generator.

What it means

OpenAPINormalizer.createNormalizer handles the NORMALIZER_CLASS rule: instead of built-in rules it reflectively loads a user-supplied class via loadNormalizerClass (thread context classloader first, then openapi-generator's own loader - deliberately friendly to Gradle Worker API isolation). ClassNotFoundException is rethrown with this guidance: the class must be visible to the classloader of the RUNNING generator process, i.e. on the launch classpath, not merely on your project's compile classpath.

Source

Thrown at modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java:190

    final String LOOSE_NULL_DEFINITIONS = "LOOSE_NULL_DEFINITIONS";

    // ============= end of rules =============

    private static final String ONE_OF_ANY_OF_ENUM_SIMPLIFIED = "Simplified {} with enum sub-schemas to single enum: {} since rule {} was enabled";

    /**
     * Factory constructor for OpenAPINormalizer.
     *
     * Default can be overriden by setting the NORMALIZER_CLASS rule
     */
    public static OpenAPINormalizer createNormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
        if (inputRules.containsKey(NORMALIZER_CLASS)) {
            String className = inputRules.get(NORMALIZER_CLASS);
            Class<?> clazz;
            try {
                clazz = loadNormalizerClass(className);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(
                        "Failed to load custom " + NORMALIZER_CLASS + " '" + className + "'. This class must be "
                                + "visible on the generation runtime classpath (i.e. resolvable either by the "
                                + "current thread's context classloader or by the classloader that loaded "
                                + "openapi-generator itself). Ensure the class (and its dependencies) is on the "
                                + "classpath used to launch the generator.", e);
            }
            try {
                Constructor<?> constructor = clazz.getConstructor(OpenAPI.class, Map.class);
                return (OpenAPINormalizer) constructor.newInstance(openAPI, inputRules);
            } catch (ReflectiveOperationException e) {
                throw new RuntimeException(
                        "Failed to instantiate custom " + NORMALIZER_CLASS + " '" + className + "'. The class was "
                                + "found but could not be constructed; it must declare a public constructor "
                                + "accepting (OpenAPI, Map<String, String>) and that constructor must not throw.", e);
            }
        } else {
            return new OpenAPINormalizer(openAPI, inputRules);
        }

View on GitHub (pinned to fcec517be3)

Solutions

  1. Fix the fully-qualified class name first: it must match package + class exactly (no spaces, correct casing).
  2. Get the class onto the GENERATOR's runtime classpath: with the Maven plugin, declare the normalizer artifact inside the plugin's <dependencies> block (not the project's); with the CLI, launch via java -cp 'your-normalizer.jar:openapi-generator-cli.jar' org.openapitools.codegen.OpenAPIGenerator or build an uber-jar including it.
  3. In embedded/Gradle contexts, set the thread context classloader (Thread.currentThread().setContextClassLoader(...)) to a loader that sees the class before invoking generation, matching what loadNormalizerClass prefers.
  4. Verify visibility at runtime before configuring: Class.forName(name, false, Thread.currentThread().getContextClassLoader()) in a smoke test.

Example fix

<!-- before: dependency only on the project, plugin can't see it -->
<plugin>
  <groupId>org.openapitools</groupId>
  <artifactId>openapi-generator-maven-plugin</artifactId>
</plugin>
<!-- after: dependency inside the plugin block -->
<plugin>
  <groupId>org.openapitools</groupId>
  <artifactId>openapi-generator-maven-plugin</artifactId>
  <dependencies>
    <dependency>
      <groupId>com.example</groupId>
      <artifactId>my-normalizer</artifactId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>
</plugin>
Defensive patterns

Strategy: validation

Validate before calling

// Verify the normalizer class is visible to the runtime BEFORE configuring it
String className = "com.example.MyNormalizer";
try {
    Class.forName(className, false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException ctxMissing) {
    try {
        Class.forName(className, false, OpenAPINormalizer.class.getClassLoader());
    } catch (ClassNotFoundException coreMissing) {
        throw new IllegalStateException(
            "NORMALIZER_CLASS not on generator runtime classpath: " + className);
    }
}
Map<String, String> rules = Map.of("NORMALIZER_CLASS", className);

Try / catch

try {
    OpenAPINormalizer.createNormalizer(openAPI, rules);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Failed to load custom NORMALIZER_CLASS")) {
        // classpath issue: add jar to plugin/CLI classpath; rethrow with remediation
        throw new IllegalStateException("Add normalizer jar to the generator's classpath", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Passing --openapi-normalizer NORMALIZER_CLASS=com.example.MyNormalizer where com.example.MyNormalizer is not on the runtime classpath: using the standalone openapi-generator-cli jar without your classes; Maven plugin without the normalizer artifact declared inside the PLUGIN's <dependencies>; Gradle setup where the worker action classloader cannot see the class; simple FQCN typo or stale package after a rename.

Common situations: Writing a first custom normalizer and testing via the downloaded CLI; adding the dependency to the project instead of the maven-plugin block; renaming packages and forgetting the option string; shaded jars built without the normalizer module.

Related errors


AI-assisted analysis of OpenAPITools/openapi-generator@fcec517be3 (2026-08-22). Data as JSON: /api/errors/0e7f46ded2ecf9c0. Report an issue: GitHub.