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

  1. Guard the refresh: only call setNodes when the discovered collection is non-empty, otherwise keep the previous node set and log a warning.
  2. Fix the upstream discovery/selector to return at least one node.
  3. 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

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


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/cde899a1ba8a9cd5. Report an issue: GitHub.