{"record":{"id":"e8dc0975df2b9237","repo":"apache/druid","slug":"watcher-for-node-role-s-returned-an-empty-colle","errorCode":null,"errorMessage":"Watcher for node role [%s] returned an empty collection.","messagePattern":"Watcher for node role \\[(.+?)\\] returned an empty collection\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/src/main/java/org/apache/druid/discovery/BaseNodeRoleWatcher.java","lineNumber":102,"sourceCode":"      ScheduledExecutorService listenerExecutor,\n      NodeRole nodeRole\n  )\n  {\n    BaseNodeRoleWatcher nodeRoleWatcher = new BaseNodeRoleWatcher(listenerExecutor, nodeRole);\n    nodeRoleWatcher.scheduleTimeout(DEFAULT_TIMEOUT_SECONDS);\n    return nodeRoleWatcher;\n  }\n\n  public Collection<DiscoveryDruidNode> getAllNodes()\n  {\n    try {\n      awaitInitialization();\n    }\n    catch (InterruptedException ex) {\n      Thread.currentThread().interrupt();\n    }\n    if (unmodifiableNodes.isEmpty()) {\n      LOGGER.warn(\"Watcher for node role [%s] returned an empty collection.\", nodeRole.getJsonName());\n    }\n    return unmodifiableNodes;\n  }\n\n  public void registerListener(DruidNodeDiscovery.Listener listener)\n  {\n    synchronized (lock) {\n      // No need to wait on CountDownLatch, because we are holding the lock under which it could only be counted down.\n      if (cacheInitialized.getCount() == 0) {\n        // It is important to take a snapshot here as list of nodes might change by the time listeners process\n        // the changes.\n        List<DiscoveryDruidNode> currNodes = Lists.newArrayList(nodes.values());\n        safeSchedule(\n            () -> {\n              listener.nodesAdded(currNodes);\n              if (cacheInitializationTimedOut) {\n                listener.nodeViewInitializedTimedOut();\n              } else {","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/discovery/BaseNodeRoleWatcher.java#L84-L120","documentation":"BaseNodeRoleWatcher.getAllNodes() waits for cache initialization (awaitInitialization) and, if the resulting node collection is empty, logs a warning before returning it. An empty result means no ZK ephemeral nodes exist for that role — either genuinely no nodes of the role are online, or the cache never populated.","triggerScenarios":"Calling getAllNodes() (e.g. via DruidNodeDiscovery.presentNodes) for a role with no announced nodes, or when initialization timed out/interrupted (see the swallowed InterruptedException) leaving the cache empty.","commonSituations":"Querying for a service role that isn't deployed (e.g. asking for overlord nodes in a cluster without one), ZK path misconfiguration, connectivity issues preventing cache population, or calling presentNodes immediately at startup before peers announce.","solutions":["Confirm the target role's services are running and announced under the expected ZK discovery path (check /druid/discovery/... in zkCli).","Check for a preceding InterruptedException or initialization timeout log indicating the cache never fully initialized.","Verify druid.discovery.zk.paths base configuration matches the cluster the nodes announce to.","If nodes were just started, retry after discovery propagation; the empty result may be a startup race."],"exampleFix":"// before\nCollection<DruidNodeDiscovery.Node> nodes = discovery.getForNodeRole(NodeRole.COORDINATOR).getAllNodes();\n// use nodes assuming non-empty\n// after\nif (nodes.isEmpty()) {\n  throw new ISE(\"No coordinator nodes discovered; check ZK discovery path and coordinator availability\");\n}","handlingStrategy":"validation","validationCode":"DruidNodeDiscovery disco = discovery.getForNodeRole(nodeRole);\nCollection<Node> nodes = disco.getAllNodes();\nif (nodes.isEmpty()) {\n  throw new ISE(\"No nodes of role [%s] discovered; check ZK path and service availability\", nodeRole.getJsonName());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never assume presentNodes/getAllNodes returns a non-empty collection; validate before use.","Confirm the role is actually deployed and announcing under your configured discovery path.","Watch for InterruptedException/init-timeout logs indicating the cache never initialized."],"tags":["zookeeper","curator","service-discovery","empty-result"],"backgroundTag":"empty-result-set","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}