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

  1. Call start() exactly once per instance; create a new instance if you need to start again.
  2. Do not call start() after close(); replace the closed instance instead.
  3. Remove duplicate lifecycle.addAndGetRegisteredObject/start calls for this bean.
  4. 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

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.