apache/druid · error · IllegalStateException
Cannot start once already started or closed
Error message
Cannot start once already started or closed
What it means
DiscoveryServiceLocator.start() is annotated @LifecycleStart and may only be called once, and only while not closed. If the service locator has already been started or has been closed, calling start() again throws an IllegalStateException.
Source
Thrown at server/src/main/java/org/apache/druid/rpc/DiscoveryServiceLocator.java:96
return Futures.immediateFuture(ServiceLocations.closed());
} else if (initialized) {
return Futures.immediateFuture(ServiceLocations.forLocations(ImmutableSet.copyOf(locations)));
} else {
if (pendingFuture == null) {
pendingFuture = SettableFuture.create();
}
return Futures.nonCancellationPropagating(pendingFuture);
}
}
}
@LifecycleStart
public void start()
{
synchronized (this) {
if (started || closed) {
throw new ISE("Cannot start once already started or closed");
} else {
started = true;
this.discovery = discoveryProvider.getForNodeRole(nodeRole);
discovery.registerListener(listener);
}
}
}
@Override
@LifecycleStop
public void close()
{
synchronized (this) {
// Idempotent: can call close() multiple times so long as start() has already been called.
if (started && !closed) {
if (discovery != null) {
discovery.removeListener(listener);
}View on GitHub (pinned to 9b90983fd2)
Solutions
- Call start() exactly once per instance; create a new instance if you need to start again.
- Do not call start() after close(); replace the closed instance instead.
- Remove duplicate lifecycle.addAndGetRegisteredObject/start calls for this bean.
- Check wiring (e.g., Jersey/Guice modules) so the locator is managed by a single lifecycle.
Example fix
// before locator.start(); locator.close(); locator.start(); // throws // after locator.start(); locator.close(); locator = new DiscoveryServiceLocator(discoveryProvider, nodeRole, listener); locator.start();
Defensive patterns
Strategy: type-guard
Validate before calling
// Java: guard before starting
// assume 'started'/'closed' exposed or tracked by caller
if (!locatorStarted && !locatorClosed) { locator.start(); locatorStarted = true; } Type guard
boolean canStart(DiscoveryServiceLocator l, boolean startedFlag, boolean closedFlag) { return !startedFlag && !closedFlag; } Try / catch
try { locator.start(); } catch (IllegalStateException e) { if (e.getMessage().contains("Cannot start once already started")) { /* already running or closed: treat as no-op or recreate */ } else { throw e; } } Prevention
- Manage lifecycle via a single Guava Lifecycle registration
- Never restart a closed instance; construct a new one
- Avoid mixing manual start() calls with lifecycle-managed start
- Track start/close state in wrapping code
When it happens
Trigger: Calling start() twice on the same DiscoveryServiceLocator, or calling start() after close() has been invoked; double lifecycle registration (e.g., adding the same object to a Guava Lifecycle twice).
Common situations: Misconfigured DI wiring where the locator is both lifecycle-managed and manually started; test code restarting a locator without recreating it; lifecycle restart attempts after shutdown.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/b2a85c84ecc6f112.
Report an issue: GitHub.