elastic/elasticsearch · error · IllegalArgumentException
nodes must not be null or empty
Error message
nodes must not be null or empty
What it means
RestClient.setNodes(Collection<Node>) replaces the live node set used for routing and requires a non-null, non-empty collection. Rejecting empty prevents the client from entering a state where no host is routable (which would make every subsequent request fail with a less obvious error).
Source
Thrown at client/rest/src/main/java/org/elasticsearch/client/RestClient.java:234
throw new IllegalArgumentException("hosts must not be null nor empty");
}
List<Node> nodes = Arrays.stream(hosts).map(Node::new).collect(Collectors.toList());
return new RestClientBuilder(nodes);
}
/**
* Get the underlying HTTP client.
*/
public HttpAsyncClient getHttpClient() {
return this.client;
}
/**
* Replaces the nodes with which the client communicates.
*/
public synchronized void setNodes(Collection<Node> nodes) {
if (nodes == null || nodes.isEmpty()) {
throw new IllegalArgumentException("nodes must not be null or empty");
}
AuthCache authCache = new BasicAuthCache();
Map<HttpHost, Node> nodesByHost = new LinkedHashMap<>();
for (Node node : nodes) {
Objects.requireNonNull(node, "node cannot be null");
// TODO should we throw an IAE if we have two nodes with the same host?
nodesByHost.put(node.getHost(), node);
authCache.put(node.getHost(), new BasicScheme());
}
this.nodeTuple = new NodeTuple<>(Collections.unmodifiableList(new ArrayList<>(nodesByHost.values())), authCache);
this.blacklist.clear();
}
/**
* Get the list of nodes that the client knows about. The list is
* unmodifiable.
*/View on GitHub (pinned to db6a809a66)
Solutions
- Guard the refresh: only call setNodes when the discovered collection is non-empty, otherwise keep the previous node set and log a warning.
- Fix the upstream discovery/selector to return at least one node.
- Retry discovery with backoff before replacing nodes.
Example fix
// before
client.setNodes(discoveredNodes); // discoveredNodes empty after sniff
// after
if (discoveredNodes == null || discoveredNodes.isEmpty()) {
logger.warn("node refresh returned no nodes; keeping previous set");
} else {
client.setNodes(discoveredNodes);
} Defensive patterns
Strategy: validation
Validate before calling
if (discoveredNodes == null || discoveredNodes.isEmpty()) {
logger.warn("node refresh returned no nodes; retaining previous node set");
} else {
client.setNodes(discoveredNodes);
} Try / catch
try { client.setNodes(discoveredNodes); } catch (IllegalArgumentException e) { /* keep previous nodes; retry discovery later */ } Prevention
- Never call setNodes with an empty collection; keep the prior set when discovery is empty.
- Investigate recurring empty-discovery results — they indicate a cluster or selector problem.
- Log every skipped refresh so silent degradation is observable.
When it happens
Trigger: Calling setNodes with an empty list after a nodes-info refresh that returned zero eligible nodes; passing null from a discovery layer that failed; a filter that excluded all nodes.
Common situations: Sniff/node-retrieval returning empty due to a transient cluster outage; a NodeSelector filtering out every node; race where setNodes is called before discovery completes.
Related errors
- cloudId {} must begin with a human readable identifier follo
- cloudId {} did not decode to a cluster identifier correctly
- cloudId {} does not contain a valid port number
- hosts must not be null nor empty
- Can't create extra config file from {} for {} as it does not
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/cde899a1ba8a9cd5.
Report an issue: GitHub.