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 IOException

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Start the provider once per instance; create a new instance to restart
  2. Use Druid's Lifecycle to manage ordering instead of manual start calls
  3. 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

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.