OpenAPITools/openapi-generator · error · RuntimeException

Failed to instantiate custom NORMALIZER_CLASS '{className}'.

Error message

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.

What it means

The companion of error [13]: OpenAPINormalizer.createNormalizer DID load your NORMALIZER_CLASS, but the reflective construction failed - clazz.getConstructor(OpenAPI.class, Map.class) or constructor.newInstance(openAPI, inputRules) threw a ReflectiveOperationException. Concretely: no public constructor with exactly (OpenAPI, Map) parameters (NoSuchMethodException), the constructor exists but is not accessible (IllegalAccessException), or it exists and ran but THREW (InvocationTargetException - see the chained cause). The class must also be assignable to OpenAPINormalizer to survive the cast.

Source

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

    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);
        }
    }

    /**
     * Loads a custom normalizer class, preferring the current thread's context classloader (which
     * frameworks such as Gradle's Worker API set to a classloader that includes any user-supplied
     * classpath) and falling back to the classloader that defined {@link OpenAPINormalizer} itself
     * (the original, pre-existing behavior) so that normalizers already visible on the default
     * classpath keep working unchanged.
     *
     * @param className fully qualified name of the custom {@link OpenAPINormalizer} subclass
     * @return the resolved {@link Class}

View on GitHub (pinned to fcec517be3)

Solutions

  1. Declare exactly one public constructor: public MyNormalizer(OpenAPI openAPI, Map<String, String> rules) - note raw Map in the signature is what getConstructor matches.
  2. If InvocationTargetException, open the chained 'Caused by:' - your constructor threw; move risky parsing/IO out of the constructor (do it lazily on first normalize call) or make unknown rules non-fatal.
  3. Verify the class extends OpenAPINormalizer (or at least is one) so the cast after newInstance succeeds.
  4. Add a one-line unit test that reflectively calls new MyNormalizer(new OpenAPI(), Map.of()) so signature drift breaks the build, not generation.

Example fix

// before
public class MyNormalizer extends OpenAPINormalizer {
    public MyNormalizer() { super(new OpenAPI(), Map.of()); } // wrong signature
}
// after
public class MyNormalizer extends OpenAPINormalizer {
    public MyNormalizer(OpenAPI openAPI, Map<String, String> rules) {
        super(openAPI, rules);
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify the required constructor exists and is public before configuring the rule
Class<?> c = Class.forName("com.example.MyNormalizer");
java.lang.reflect.Constructor<?> ctor;
try {
    ctor = c.getConstructor(io.swagger.v3.oas.models.OpenAPI.class, java.util.Map.class);
} catch (NoSuchMethodException e) {
    throw new IllegalStateException(
        "MyNormalizer needs: public MyNormalizer(OpenAPI, Map<String,String>)", e);
}
if (!java.lang.reflect.Modifier.isPublic(ctor.getModifiers())) {
    throw new IllegalStateException("MyNormalizer constructor must be public");
}

Try / catch

try {
    OpenAPINormalizer.createNormalizer(openAPI, rules);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Failed to instantiate custom NORMALIZER_CLASS")) {
        Throwable root = e.getCause() != null ? e.getCause().getCause() : null; // InvocationTargetException target
        // root != null => constructor threw; fix that inner failure, not the reflection call
        throw new IllegalStateException("Normalizer constructor failed", root != null ? root : e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Declaring the normalizer constructor as (OpenAPI, Map<String,String>) with generics is fine, but adding/omitting a parameter, making it protected/private, throwing IllegalArgumentException on an unrecognized rule inside the constructor, or doing IO in the constructor that fails - each produces this error right after the class loaded successfully.

Common situations: First custom normalizers modelled on examples that validate rules eagerly in the constructor; refactoring a normalizer's constructor signature and not re-checking the contract; constructors reading files/system properties that are absent in CI.

Related errors


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