apache/dubbo · critical · IllegalStateException
ExtensionDirector is destroyed
Error message
ExtensionDirector is destroyed
What it means
Thrown by ExtensionDirector.checkDestroyed() when any method is called on an ExtensionDirector whose destroy() method has already been invoked. ExtensionDirector is tied to a ScopeModel (application, module) and when that scope is shut down, the director and all its child ExtensionLoaders are destroyed. Any subsequent access is illegal.
Source
Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionDirector.java:156
return parent;
}
public void removeAllCachedLoader() {}
public void destroy() {
if (destroyed.compareAndSet(false, true)) {
for (ExtensionLoader<?> extensionLoader : extensionLoadersMap.values()) {
extensionLoader.destroy();
}
extensionLoadersMap.clear();
extensionScopeMap.clear();
extensionPostProcessors.clear();
}
}
private void checkDestroyed() {
if (destroyed.get()) {
throw new IllegalStateException("ExtensionDirector is destroyed");
}
}
}
View on GitHub (pinned to 3a3043227f)
Solutions
- Inspect the stack trace: identify the caller that triggers extension access after shutdown.
- Ensure background threads and scheduled tasks are stopped before the ScopeModel/ExtensionDirector is destroyed.
- If using DubboBootstrap, ensure all consumers/providers are fully deregistered before application exit.
- Guard the call site with a check against the scope model's lifecycle state, or use a shutdown latch.
Example fix
// before — background task accesses extensions after shutdown
scheduledExecutor.scheduleAtFixedRate(() -> {
director.getExtensionLoader(MySpi.class).getExtension("impl");
}, 0, 1, TimeUnit.SECONDS);
// after — stop the task before shutdown
ScheduledFuture<?> future = scheduledExecutor.scheduleAtFixedRate(...);
// during shutdown:
future.cancel(false);
scheduledExecutor.shutdown();
dubboBootstrap.stop(); Defensive patterns
Strategy: try-catch
Validate before calling
// ExtensionDirector has no public isDestroyed() method.
// Track lifecycle externally or wrap access in a try-catch.
// If you control the ScopeModel, gate access with your own shutdown flag.
if (!applicationShuttingDown.get()) {
ExtensionLoader<T> loader = director.getExtensionLoader(type);
} Try / catch
try {
ExtensionLoader<T> loader = director.getExtensionLoader(type);
} catch (IllegalStateException e) {
if (e.getMessage().equals("ExtensionDirector is destroyed")) {
log.warn("ExtensionDirector already destroyed — skipping extension access", e);
return null; // or fallback
}
throw e;
} Prevention
- Stop all background threads and scheduled tasks before calling dubboBootstrap.stop() or ScopeModel.destroy().
- Use a shutdown latch or flag to gate extension access in async callbacks.
- In integration tests, destroy the application context in @AfterAll, not @AfterEach, and avoid extension access in cleanup.
- Never cache ExtensionDirector references in static fields across application restarts.
When it happens
Trigger: Application shutdown sequence destroys the ScopeModel and its ExtensionDirector, but a background thread, timer, or lazy-init callback subsequently tries to resolve or access an extension. Also triggered by manual calls to director.destroy() followed by continued usage. Common in hot-reload, redeploy, or graceful-shutdown scenarios.
Common situations: Graceful shutdown where a lingering RPC callback or scheduled task triggers extension lookup after shutdown. Integration tests that destroy the application context but continue assertions. A custom lifecycle hook calls destroy() too early. Framework restart in a container (e.g., Tomcat undeploy/redeploy) leaves stale references.
Related errors
- ExtensionLoader is destroyed: ${type}
- This instance has been destroyed
- ScopeBeanFactory is destroyed
- Exception occurred when loading extension class (interface:
- Executor is shutdown!
AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14).
Data as JSON: /api/errors/3d5f189c9740054b.
Report an issue: GitHub.