{"record":{"id":"2c154c7c7568b9b2","repo":"quarkusio/quarkus","slug":"instance-already-destroyed","errorCode":null,"errorMessage":"Instance already destroyed","messagePattern":"Instance already destroyed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java","lineNumber":40,"sourceCode":"    private final CreationalContext<T> creationalContext;\n    private final CreationalContext<?> parentCreationalContext;\n    private final Consumer<T> destroyLogic;\n\n    // values: 0=\"not destroyed\", 1=\"destroyed\"\n    private volatile int destroyed;\n\n    AbstractInstanceHandle(InjectableBean<T> bean, CreationalContext<T> creationalContext,\n            CreationalContext<?> parentCreationalContext, Consumer<T> destroyLogic) {\n        this.bean = bean;\n        this.creationalContext = creationalContext;\n        this.parentCreationalContext = parentCreationalContext;\n        this.destroyLogic = destroyLogic;\n    }\n\n    @Override\n    public T get() {\n        if (destroyed != 0) {\n            throw new IllegalStateException(\"Instance already destroyed\");\n        }\n        return instanceInternal();\n    }\n\n    @Override\n    public InjectableBean<T> getBean() {\n        return bean;\n    }\n\n    protected abstract boolean isInstanceCreated();\n\n    protected abstract T instanceInternal();\n\n    @Override\n    public void destroy() {\n        if (isInstanceCreated() && DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {\n            if (destroyLogic != null) {\n                destroyLogic.accept(instanceInternal());","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java#L22-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call get() before destroy(), and keep the returned reference for later use","Do not destroy a handle while it may still be read; re-obtain a fresh handle via Arc.container().instance(...) if needed","Track destroyed state yourself and skip get() when already destroyed","Check for accidental double-destroy logic (destroy called twice, then get)"],"exampleFix":"// before\nhandle.destroy();\nFoo foo = handle.get(); // IllegalStateException\n// after\nFoo foo = handle.get(); // obtain first\nhandle.destroy(); // then destroy","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"boolean usable = (handle instanceof InstanceHandle<?> h) && !h.isDestroyed();","tryCatchPattern":"try { Foo f = handle.get(); } catch (IllegalStateException e) { Foo f = Arc.container().instance(Foo.class).get(); }","preventionTips":["Consume get() before calling destroy()","Never cache InstanceHandle longer than its lifecycle owner","Centralize destroy logic to avoid double-destroy/use-after-destroy"],"tags":["cdi","lifecycle","use-after-destroy"],"backgroundTag":"instance-already-destroyed","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}