apache/druid · error · IllegalStateException

Lifecycle has already started.

Error message

Lifecycle has already started.

What it means

registerSegmentCallback(Executor, SegmentCallback, Predicate) must be invoked before the server inventory view starts. Once lifecycleLock.isStarted() is true, registering new callbacks is rejected with ISE because the registration maps feed the sync executor only at startup.

Solutions

  1. Register all callbacks before calling start() or before the Lifecycle begins
  2. Restructure so callbacks are known at construction time and passed via configuration/DI
  3. Use a fresh HttpServerInventoryView instance if registration must happen later
  4. If dynamic registration is needed, wrap the callback to change its behavior rather than re-registering

Example fix

// before
view.start();
view.registerSegmentCallback(exec, cb, filter); // ISE
// after
view.registerSegmentCallback(exec, cb, filter);
view.start();
Defensive patterns

Strategy: validation

Validate before calling

// register only before start
if (!isStarted) view.registerSegmentCallback(exec, cb, filter);

Prevention

When it happens

Trigger: Calling the filtered registerSegmentCallback after start() has run (or after the enclosing Lifecycle started the view).

Common situations: Broker extension plugins adding segment filters lazily after service startup; race between a management thread and lifecycle startup; tests that add callbacks in @BeforeEach while a shared instance was already started.

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/ab6f38c343fb479b. Report an issue: GitHub.

Appendix: source

Thrown at server/src/main/java/org/apache/druid/client/HttpServerInventoryView.java:285

        inventorySyncExecutor.shutdownNow();
      }
      if (monitoringExecutor != null) {
        monitoringExecutor.shutdownNow();
      }

      log.info("Stopped executor[%s].", execNamePrefix);
    }
  }

  @Override
  public void registerSegmentCallback(
      Executor exec,
      SegmentCallback callback,
      Predicate<Pair<DruidServerMetadata, DataSegment>> filter
  )
  {
    if (lifecycleLock.isStarted()) {
      throw new ISE("Lifecycle has already started.");
    }

    SegmentCallback filteringSegmentCallback = new FilteringSegmentCallback(callback, filter);
    segmentCallbacks.put(filteringSegmentCallback, exec);
    segmentPredicates.put(filteringSegmentCallback, filter);

    updateFinalPredicate();
  }

  @Override
  public void registerServerCallback(Executor exec, ServerCallback callback)
  {
    if (lifecycleLock.isStarted()) {
      throw new ISE("Lifecycle has already started.");
    }

    serverCallbacks.put(callback, exec);
  }

View on GitHub (pinned to 9b90983fd2)