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
- Declare exactly one public constructor: public MyNormalizer(OpenAPI openAPI, Map<String, String> rules) - note raw Map in the signature is what getConstructor matches.
- 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.
- Verify the class extends OpenAPINormalizer (or at least is one) so the cast after newInstance succeeds.
- 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
- Keep a unit test that reflectively invokes new MyNormalizer(new OpenAPI(), Map.of()) so signature drift fails the build.
- Do no IO or strict validation inside the normalizer constructor; defer to first use.
- Extend OpenAPINormalizer so the post-instantiation cast always succeeds.
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
- Can't instantiate config class with name '{name}'. The class
- Failed to load custom NORMALIZER_CLASS '{className}'. This c
- Issues with the OpenAPI input. Possible causes: invalid/miss
- missing config!
- useJackson3 is only supported for the 'native', 'apache-http
AI-assisted analysis of OpenAPITools/openapi-generator@fcec517be3 (2026-08-22).
Data as JSON: /api/errors/2456dfe93d67f2e2.
Report an issue: GitHub.