apache/druid · error · IllegalStateException
can't start.
Error message
can't start.
What it means
Thrown by CoordinatorPollingBasicAuthenticatorCacheManager.start when lifecycleLock.canStart() returns false. This manager polls the coordinator for authenticator user maps; like all Druid lifecycle components it may only transition from an idle state into started. Double-start, start-after-stop, or concurrent start calls all fail here.
Source
Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authentication/db/cache/CoordinatorPollingBasicAuthenticatorCacheManager.java:102
BasicAuthCommonCacheConfig commonCacheConfig,
@Smile ObjectMapper objectMapper,
@Coordinator ServiceClient coordinatorClient
)
{
this.exec = Execs.scheduledSingleThreaded("BasicAuthenticatorCacheManager-Exec--%d");
this.injector = injector;
this.commonCacheConfig = commonCacheConfig;
this.objectMapper = objectMapper;
this.cachedUserMaps = new ConcurrentHashMap<>();
this.authenticatorPrefixes = new HashSet<>();
this.coordinatorClient = coordinatorClient;
}
@LifecycleStart
public void start()
{
if (!lifecycleLock.canStart()) {
throw new ISE("can't start.");
}
LOG.info("Starting CoordinatorPollingBasicAuthenticatorCacheManager.");
try {
initUserMaps();
ScheduledExecutors.scheduleWithFixedDelay(
exec,
new Duration(commonCacheConfig.getPollingPeriod()),
new Duration(commonCacheConfig.getPollingPeriod()),
() -> {
try {
long randomDelay = ThreadLocalRandom.current().nextLong(0, commonCacheConfig.getMaxRandomDelay());
LOG.debug("Inserting cachedUserMaps random polling delay of [%s] ms", randomDelay);
Thread.sleep(randomDelay);
LOG.debug("Scheduled user cache poll is running");View on GitHub (pinned to 9b90983fd2)
Solutions
- Call stop() and create a fresh instance instead of re-starting a started/stopped manager
- Verify the manager is registered once in the lifecycle and started exactly once
- Guard callers with a state check before invoking start
- In tests, stop the manager in @AfterEach to reset state
Example fix
// before
manager.start(); manager.start(); // second call throws
// after
if (!started) { manager.start(); started = true; } Defensive patterns
Strategy: type-guard
Validate before calling
boolean startOnce(AtomicBoolean started, CoordinatorPollingBasicAuthenticatorCacheManager m) { return started.compareAndSet(false, true) && (m.start(), true); } Type guard
boolean isStartable(CoordinatorPollingBasicAuthenticatorCacheManager m) { try { return m != null && !m.started(); } catch (UnsupportedOperationException e) { return true; } } // track your own started flag Try / catch
try { manager.start(); } catch (ISE e) { LOG.warn("cache manager start rejected (already started/stopped)"); } Prevention
- Register the manager once in the lifecycle and start via the framework only
- In tests, stop the manager in @AfterEach and recreate per test
- Guard start calls with an idempotency flag
- Keep leadership-change handlers restart-safe (stop, await, recreate)
When it happens
Trigger: start() called twice on the same manager instance; start() after stop(); racing lifecycle threads where one thread already claimed the transition; test code manually starting the manager after the suite lifecycle already did.
Common situations: Incorrect lifecycle registration order; test teardown not stopping the manager between cases; leadership handover code restarting the manager without proper stop first.
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/8f71211f10f55180.
Report an issue: GitHub.