elastic/elasticsearch · error · IOException

expected data to start with an object

Error message

expected data to start with an object

What it means

Thrown by readHosts when parsing the /_nodes/http response: the first JSON token must be START_OBJECT (the outer nodes-info envelope). If the parser sees an array, a string, or anything else, the response is not a valid nodes-info document and sniffer cannot extract hosts.

Source

Thrown at client/sniffer/src/main/java/org/elasticsearch/client/sniff/ElasticsearchNodesSniffer.java:114

        this.request = new Request("GET", "/_nodes/http");
        request.addParameter("timeout", sniffRequestTimeoutMillis + "ms");
        this.scheme = Objects.requireNonNull(scheme, "scheme cannot be null");
    }

    /**
     * Calls the elasticsearch nodes info api, parses the response and returns all the found http hosts
     */
    @Override
    public List<Node> sniff() throws IOException {
        Response response = restClient.performRequest(request);
        return readHosts(response.getEntity(), scheme, jsonFactory);
    }

    static List<Node> readHosts(HttpEntity entity, Scheme scheme, JsonFactory jsonFactory) throws IOException {
        try (InputStream inputStream = entity.getContent()) {
            JsonParser parser = jsonFactory.createParser(inputStream);
            if (parser.nextToken() != JsonToken.START_OBJECT) {
                throw new IOException("expected data to start with an object");
            }
            List<Node> nodes = new ArrayList<>();
            while (parser.nextToken() != JsonToken.END_OBJECT) {
                if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
                    if ("nodes".equals(parser.getCurrentName())) {
                        while (parser.nextToken() != JsonToken.END_OBJECT) {
                            JsonToken token = parser.nextToken();
                            assert token == JsonToken.START_OBJECT;
                            String nodeId = parser.getCurrentName();
                            Node node = readNode(nodeId, parser, scheme);
                            if (node != null) {
                                nodes.add(node);
                            }
                        }
                    } else {
                        parser.skipChildren();
                    }
                }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Verify the sniff endpoint (/_nodes/http) returns the standard ES nodes-info object: curl it directly.
  2. Ensure the RestClient is pointed at the real ES HTTP layer, not a proxy/HTML gateway.
  3. If behind a proxy, configure the proxy to pass through to ES unchanged, or sniff a fixed host list instead.

Example fix

// before
RestClient client = RestClient.builder(new HttpHost("proxy", 80, "http")).build();
Sniffer sniffer = Sniffer.builder(client).build();
// after
RestClient client = RestClient.builder(new HttpHost("es-internal", 9200, "http")).build();
Sniffer sniffer = Sniffer.builder(client).build();
Defensive patterns

Strategy: try-catch

Try / catch

try { sniffer.sniff(); }
catch (IOException e) {
    if (e.getMessage().equals("expected data to start with an object")) {
        // endpoint returned a non-ES body: disable sniffing or fix the proxy
    } else throw e;
}

Prevention

When it happens

Trigger: The endpoint returned a non-JSON-object body: an array of errors, a plain string, HTML error page, or a proxy interstitial. Reached via Sniffer.sniff() -> ElasticsearchNodesSniffer.sniff().

Common situations: Sniffing against a URL that is fronted by a load balancer/proxy returning a different shape; wrong scheme/port hitting a non-ES service; ES returned an error array; authentication challenge returning an HTML page.

Related errors


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