quarkusio/quarkus · error · IllegalStateException

Classloader is not resettable

Error message

Classloader is not resettable

What it means

QuarkusClassLoader.reset() can only be called on a classloader that was created with a resettable element (a memory class-path element that supports regeneration). If the loader has no resettableElement, reset() throws IllegalStateException("Classloader is not resettable").

Source

Thrown at independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java:233

        if (name.startsWith("/")) {
            name = name.substring(1);
        }
        if (name.endsWith("/")) {
            name = name.substring(0, name.length() - 1);
        }
        return name;
    }

    private boolean parentFirst(String name, ClassPathResourceIndex classPathResourceIndex) {
        return parentFirst || name.startsWith("io/quarkus/devservices/crossclassloader")
                || classPathResourceIndex.isParentFirst(name);
    }

    public void reset(Map<String, byte[]> generatedResources, Map<String, byte[]> transformedClasses) {
        ensureOpen();

        if (resettableElement == null) {
            throw new IllegalStateException("Classloader is not resettable");
        }
        synchronized (this) {
            // we don't want the previous MemoryClassPathElement to leak as a key of protectionDomains
            protectionDomains.remove(this.transformedClasses);
            this.transformedClasses = new MemoryClassPathElement(transformedClasses, true);
            resettableElement.reset(generatedResources);
            classPathResourceIndex = null;
        }
    }

    /**
     * Returns all in-memory generated and transformed class bytes, keyed by
     * resource path (e.g. {@code io/quarkus/runner/Foo.class}).
     * <p>
     * Classes are collected in classloader priority order: transformed classes
     * first, then resettable element, then normal priority elements, then
     * lesser priority elements. Only {@link MemoryClassPathElement} instances
     * are included. Uses {@code putIfAbsent} so higher-priority entries win.

View on GitHub (pinned to e1c734241f)

Solutions

  1. Only call reset() on the QuarkusClassLoader instance created for dev mode / the StartupAction, not the base loader
  2. Check how the classloader was constructed and enable the resettable memory element if you control creation
  3. Replace the classloader (restart the StartupAction) instead of resetting when the loader is not resettable

Example fix

// before
baseQuarkusClassLoader.reset(newResources, transformed); // IllegalStateException
// after
QuarkusClassLoader appLoader = (QuarkusClassLoader) startupAction.getClassLoader();
appLoader.reset(newResources, transformed);
Defensive patterns

Strategy: type-guard

Validate before calling

public static void resetIfResettable(QuarkusClassLoader cl, Map<String, byte[]> gen, Map<String, byte[]> transformed) {
    if (cl == null) throw new IllegalStateException("No application classloader");
    cl.reset(gen, transformed);
}

Type guard

boolean isResettable(QuarkusClassLoader cl) {
    // only dev-mode / startup-action loaders are resettable
    return cl != null && cl.getParent() != null
        && "io.quarkus.dev.runtime.QuarkusClassLoader".equals(cl.getClass().getName()) == false
        ? cl.getClass().getName().contains("QuarkusClassLoader") : cl != null;
}
// safer: track the loader you got from the StartupAction and only reset that instance

Try / catch

try {
    loader.reset(generated, transformed);
} catch (IllegalStateException e) {
    // loader not resettable -> restart the StartupAction instead of resetting
}

Prevention

When it happens

Trigger: Calling QuarkusClassLoader.reset(generatedResources, transformedClasses) on a loader built without the resettable flag — e.g. a base/production QuarkusClassLoader rather than the dev-mode or test resettable loader.

Common situations: Invoking reset() from custom tooling on a non-resettable classloader; live-reload code paths executed against a loader created without reset support; mixing up the base loader with the application loader in tests.

Related errors


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