quarkusio/quarkus · error · IllegalStateException

Instance already destroyed

Error message

Instance already destroyed

What it means

AbstractInstanceHandle.get() throws IllegalStateException if the handle has already been destroyed. InstanceHandle/InstanceHandleImpl and similar wrappers track a destroyed flag; once destroy() runs, the instance is no longer retrievable. This guards use-after-free of contextual instances.

Source

Thrown at independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java:40

    private final CreationalContext<T> creationalContext;
    private final CreationalContext<?> parentCreationalContext;
    private final Consumer<T> destroyLogic;

    // values: 0="not destroyed", 1="destroyed"
    private volatile int destroyed;

    AbstractInstanceHandle(InjectableBean<T> bean, CreationalContext<T> creationalContext,
            CreationalContext<?> parentCreationalContext, Consumer<T> destroyLogic) {
        this.bean = bean;
        this.creationalContext = creationalContext;
        this.parentCreationalContext = parentCreationalContext;
        this.destroyLogic = destroyLogic;
    }

    @Override
    public T get() {
        if (destroyed != 0) {
            throw new IllegalStateException("Instance already destroyed");
        }
        return instanceInternal();
    }

    @Override
    public InjectableBean<T> getBean() {
        return bean;
    }

    protected abstract boolean isInstanceCreated();

    protected abstract T instanceInternal();

    @Override
    public void destroy() {
        if (isInstanceCreated() && DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
            if (destroyLogic != null) {
                destroyLogic.accept(instanceInternal());

View on GitHub (pinned to e1c734241f)

Solutions

  1. Call get() before destroy(), and keep the returned reference for later use
  2. Do not destroy a handle while it may still be read; re-obtain a fresh handle via Arc.container().instance(...) if needed
  3. Track destroyed state yourself and skip get() when already destroyed
  4. Check for accidental double-destroy logic (destroy called twice, then get)

Example fix

// before
handle.destroy();
Foo foo = handle.get(); // IllegalStateException
// after
Foo foo = handle.get(); // obtain first
handle.destroy(); // then destroy
Defensive patterns

Strategy: type-guard

Type guard

boolean usable = (handle instanceof InstanceHandle<?> h) && !h.isDestroyed();

Try / catch

try { Foo f = handle.get(); } catch (IllegalStateException e) { Foo f = Arc.container().instance(Foo.class).get(); }

Prevention

When it happens

Trigger: Calling handle.get() after handle.destroy() on the same InstanceHandle, e.g. storing the handle in a field, consuming it, then re-reading it later in request shutdown or cleanup code.

Common situations: Caching an InstanceHandle across requests and destroying it at the end of one request; double-cleanup in @PreDestroy or shutdown hooks calling destroy then get; frameworks that auto-destroy handles on context close.

Related errors


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