quarkusio/quarkus · error · IllegalStateException

Template builder already specified for: ${rootCauseClassName

Error message

Template builder already specified for: ${rootCauseClassName}

What it means

ErrorPageGenerators.register() maps a root-cause exception class name to a function that renders a custom error page in dev mode. Only one generator per class is allowed; registering a second generator for the same class name throws this IllegalStateException.

Source

Thrown at core/devmode-spi/src/main/java/io/quarkus/dev/ErrorPageGenerators.java:26

 * The generators can be used to generate a custom HTML page for a specific deployment exception that occurs during the
 * development mode.
 * <p>
 * In order to avoid classloading issues the generators should not access the root cause directly but use reflection instead
 * (the exception class could be loaded by a different class loader).
 */
public class ErrorPageGenerators {

    private static final Map<String, Function<Throwable, String>> generators = new ConcurrentHashMap<>();

    /**
     * Register a function that will be used to generate the error page for the given root cause.
     *
     * @param rootCauseClassName
     * @param function
     */
    public static void register(String rootCauseClassName, Function<Throwable, String> function) {
        if (generators.putIfAbsent(rootCauseClassName, function) != null) {
            throw new IllegalStateException("Template builder already specified for: " + rootCauseClassName);
        }
    }

    public static Function<Throwable, String> get(String rootCauseClassName) {
        return generators.get(rootCauseClassName);
    }

    // This method is called by a relevant service provider during HotReplacementSetup#close()
    public static void clear() {
        generators.clear();
    }

}

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove one of the duplicate register() calls for the same class name
  2. Guard your registration with ErrorPageGenerators.get(className) == null before registering
  3. On hot reload, make registration idempotent (check before register)

Example fix

// before
ErrorPageGenerators.register("com.acme.MyException", fn);
// after
if (ErrorPageGenerators.get("com.acme.MyException") == null) {
    ErrorPageGenerators.register("com.acme.MyException", fn);
}
Defensive patterns

Strategy: validation

Validate before calling

String cls = "com.acme.MyException";
if (ErrorPageGenerators.get(cls) == null) {
    ErrorPageGenerators.register(cls, fn);
}

Try / catch

try { ErrorPageGenerators.register(cls, fn); } catch (IllegalStateException e) { if (e.getMessage().startsWith("Template builder already specified")) { /* skip or replace existing registration */ } else { throw e; } }

Prevention

When it happens

Trigger: Calling ErrorPageGenerators.register(className, fn) twice with the same rootCauseClassName — e.g. two extensions or a hot-redeployed classpath registering the same exception handler again.

Common situations: Two extensions both registering handlers for the same exception type; dev-mode hot reload re-invoking registration code; user code registering a custom handler that collides with an extension's handler.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/5e00ca5eb08fa294. Report an issue: GitHub.