apache/druid · error · IllegalStateException
can't start.
Error message
can't start.
What it means
Thrown by CoordinatorBasicAuthenticatorMetadataStorageUpdater.start when lifecycleLock.canStart() returns false. This updater writes authenticator user maps to metadata storage; it must transition once from idle to started. Double-start, start-after-stop, or concurrent start attempts are rejected here as illegal state transitions.
Source
Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/db/updater/CoordinatorBasicAuthenticatorMetadataStorageUpdater.java:113
// set a dependency here
)
{
this.exec = Execs.scheduledSingleThreaded("CoordinatorBasicAuthenticatorMetadataStorageUpdater-Exec--%d");
this.authenticatorMapper = authenticatorMapper;
this.connector = connector;
this.connectorConfig = connectorConfig;
this.commonCacheConfig = commonCacheConfig;
this.objectMapper = objectMapper;
this.cacheNotifier = cacheNotifier;
this.cachedUserMaps = new ConcurrentHashMap<>();
this.authenticatorPrefixes = new HashSet<>();
}
@LifecycleStart
public void start()
{
if (!lifecycleLock.canStart()) {
throw new ISE("can't start.");
}
if (authenticatorMapper == null || authenticatorMapper.getAuthenticatorMap() == null) {
return;
}
try {
LOG.info("Starting CoordinatorBasicAuthenticatorMetadataStorageUpdater.");
BasicAuthUtils.maybeInitialize(
() -> {
for (Map.Entry<String, Authenticator> entry : authenticatorMapper.getAuthenticatorMap().entrySet()) {
Authenticator authenticator = entry.getValue();
if (authenticator instanceof BasicHTTPAuthenticator) {
String authenticatorName = entry.getKey();
authenticatorPrefixes.add(authenticatorName);
BasicHTTPAuthenticator basicHTTPAuthenticator = (BasicHTTPAuthenticator) authenticator;
BasicAuthDBConfig dbConfig = basicHTTPAuthenticator.getDbConfig();
byte[] userMapBytes = getCurrentUserMapBytes(authenticatorName);View on GitHub (pinned to 9b90983fd2)
Solutions
- Create a fresh updater instance rather than restarting a stopped one
- Confirm the updater's @LifecycleStart handler is registered once and invoked once
- Serialize lifecycle transitions (single owner thread/leader invokes start/stop)
- Check logs for a prior start/stop that already moved the lifecycle lock
Example fix
// before
updater.start();
// after
if (lifecycleLock.canStart()) { updater.start(); } // or recreate instance after stop Defensive patterns
Strategy: type-guard
Validate before calling
if (!updaterStarted.compareAndSet(false, true)) { return; } updater.start(); Type guard
boolean canStart(CoordinatorBasicAuthenticatorMetadataStorageUpdater u) { return u != null && !stoppedFlag.get(); } Try / catch
try { updater.start(); } catch (ISE e) { LOG.warn("updater start rejected (already started/stopped)"); } Prevention
- Let the Druid Lifecycle invoke @LifecycleStart handlers once
- Recreate the updater after stop rather than calling start again
- Guard leadership-transition restart logic to avoid double start
- Serialize lifecycle transitions through a single owner
When it happens
Trigger: Calling start() twice on the updater; start() after stop() (stopped=true already set); lifecycle races between leadership election callbacks; manually starting in tests after framework start.
Common situations: Misordered lifecycle handler registration; restarting the updater on coordinator leadership change without proper stop; test suites reusing 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/0c1104266b1151aa.
Report an issue: GitHub.