openzipkin/zipkin · error · IllegalArgumentException
No content reading cluster health
Error message
No content reading cluster health
What it means
ElasticsearchStorage.ensureIndexTemplatesAndClusterReady(index) performs the one-time index-template installation and then issues GET /_cluster/health/<index> through the READ_STATUS body converter. The converter is expected to always produce a CheckResult; result == null means the HTTP call completed but no content/body was converted — i.e. the cluster-health response was empty — and that is surfaced as IllegalArgumentException instead of a silent pass.
Source
Thrown at zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchStorage.java:296
/** This is blocking so that we can determine if the cluster is healthy or not */
@Override public CheckResult check() {
return ensureIndexTemplatesAndClusterReady(indexNameFormatter().formatType(TYPE_SPAN));
}
/**
* This allows the health check to display problems, such as access, installing the index
* template. It also helps reduce traffic sent to nodes still initializing (when guarded on the
* check result). Finally, this reads the cluster health of the index as it can go down after the
* one-time initialization passes.
*/
CheckResult ensureIndexTemplatesAndClusterReady(String index) {
try {
version(); // ensure the version is available (even if we already cached it)
ensureIndexTemplates(); // called only once, so we have to double-check health
AggregatedHttpRequest request = AggregatedHttpRequest.of(GET, "/_cluster/health/" + index);
CheckResult result = http().newCall(request, READ_STATUS, "get-cluster-health").execute();
if (result == null) throw new IllegalArgumentException("No content reading cluster health");
return result;
} catch (Throwable e) {
Call.propagateIfFatal(e);
// Wrapping interferes with humans intended to read this message:
//
// Unwrap the marker exception as the health check is not relevant for the throttle component.
// Unwrap any IOException from the first call to ensureIndexTemplates()
if (e instanceof RejectedExecutionException || e instanceof UncheckedIOException) {
return CheckResult.failed(e.getCause());
}
return CheckResult.failed(e);
}
}
volatile boolean ensuredTemplates;
// synchronized since we don't want overlapping calls to apply the index templates
void ensureIndexTemplates() {View on GitHub (pinned to 878ce2a1fa)
Solutions
- curl -v the exact URL Zipkin uses (http://<host>:9200/_cluster/health/zipkin-span-<date>) and confirm a JSON body with a status field is returned.
- Fix the proxy/ingress in front of Elasticsearch so it passes the response body through unchanged.
- Verify ES_HOSTS points at real Elasticsearch/OpenSearch HTTP endpoints (port 9200, not the transport port).
- The exception is wrapped into CheckResult.failed by the surrounding catch, so the message appears in the health endpoint — use it to diagnose rather than crash.
Example fix
# before: proxy returns empty 200 for /_cluster/health/*
# -> IllegalArgumentException: No content reading cluster health
# after: verify direct node response and bypass/fix the proxy
$ curl -s http://es:9200/_cluster/health/zipkin-span-2026-08-14
{"cluster_name":"es","status":"green",...} Defensive patterns
Strategy: retry
Validate before calling
// smoke-test the exact health URL before/at startup
try (var client = HttpClient.newHttpClient()) {
var resp = client.send(HttpRequest.newBuilder(URI.create(esHost + "/_cluster/health/zipkin-span")).GET().build(),
HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() != 200 || resp.body().isEmpty()) {
throw new IllegalStateException("Proxy/node returned empty cluster health body");
}
} Try / catch
// health checks return CheckResult instead of throwing; inspect it
CheckResult health = storage.check();
if (!health.ok()) {
// log health.error(); retry or alert — this error appears as its message
} Prevention
- Smoke-test /_cluster/health/<index> through the exact proxy path Zipkin uses before deploying.
- Avoid LB/proxy rules that rewrite or empty ES responses (especially 200-with-empty-body).
- Treat check() failures as alerts, not crashes; the underlying cause is in CheckResult.error().
When it happens
Trigger: The health-check path (CheckResult check() / first query after startup) when /_cluster/health/<zipkin*> returns an empty body: e.g. an intermediate proxy or load balancer answering 200 with zero bytes, a misrouted host, or a node closing the response early.
Common situations: Elasticsearch behind nginx/HAProxy/Kubernetes ingress that strips or buffers responses; connecting through a service mesh; a URL with a wrong port hitting a non-ES service.
Related errors
- Health status couldn't be read %s
- Invalid .version.number: %s, for .version.distribution:%s
- key == null
- endTs <= 0
- Elasticsearch versions 5-9.x are supported, was: %s
AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14).
Data as JSON: /api/errors/1450472e61826943.
Report an issue: GitHub.