apache/druid · error · IllegalStateException
can't start.
Error message
can't start.
What it means
CuratorDruidNodeDiscoveryProvider.start() is annotated @LifecycleStart and uses a LifecycleLock permitting only one successful start. If canStart() returns false (already started, concurrently starting, or previously stopped), it throws ISE("can't start.").
Source
Thrown at server/src/main/java/org/apache/druid/curator/discovery/CuratorDruidNodeDiscoveryProvider.java:129
log.debug("Creating NodeRoleWatcher for nodeRole [%s].", role);
NodeRoleWatcher nodeRoleWatcher = new NodeRoleWatcher(
listenerExecutor,
curatorFramework,
config.getInternalDiscoveryPath(),
jsonMapper,
role
);
log.debug("Created NodeRoleWatcher for nodeRole [%s].", role);
return nodeRoleWatcher;
}
);
}
@LifecycleStart
public void start()
{
if (!lifecycleLock.canStart()) {
throw new ISE("can't start.");
}
try {
// This is single-threaded to ensure that all listener calls are executed precisely in the order of add/remove
// event occurrences.
listenerExecutor = Execs.scheduledSingleThreaded("CuratorDruidNodeDiscoveryProvider-ListenerExecutor");
log.debug("Started.");
lifecycleLock.started();
}
finally {
lifecycleLock.exitStart();
}
}
@LifecycleStop
public void stop() throws IOExceptionView on GitHub (pinned to 9b90983fd2)
Solutions
- Start the provider once per instance; create a new instance to restart
- Use Druid's Lifecycle to manage ordering instead of manual start calls
- Synchronize callers or use lifecycleLock.awaitStarted() to wait rather than double-start
Example fix
// before provider.start(); provider.start(); // after provider.start(); // once; later callers should wait via lifecycleLock.awaitStarted()
Defensive patterns
Strategy: try-catch
Validate before calling
if (!lifecycleLock.canStart()) {
throw new IllegalStateException("Provider already started or starting");
} Type guard
boolean needsStart = lifecycleLock.canStart();
Try / catch
try {
provider.start();
} catch (IllegalStateException e) {
if ("can't start.".equals(e.getMessage())) {
lifecycleLock.awaitStarted(); // already started: just wait for readiness
} else {
throw e;
}
} Prevention
- Start the provider exactly once per instance
- Use a shared Lifecycle instead of manual start calls
- Avoid racing start() from multiple threads
When it happens
Trigger: Calling start() twice, calling start() concurrently from two threads, or calling start() after stop() without re-creating the instance; observed via the testAnnouncementAndDiscovery path.
Common situations: Double-registration of the provider in a Lifecycle, race between server startup and a test calling start directly, restart attempts on a stopped instance.
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/706fff8ef2d90dc2.
Report an issue: GitHub.