openzipkin/zipkin · error · IllegalArgumentException

Health status couldn't be read %s

Error message

Health status couldn't be read %s

What it means

READ_STATUS.convert parses the /_cluster/health/<index> response and looks for the top-level 'status' field (enterPath(parser, "status")). If the JSON has no status field, it throws IllegalArgumentException including the raw body via contentString.get(), so the actual server response is part of the message. It also maps status RED to a failed CheckResult with 'Health status is RED'.

Source

Thrown at zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchStorage.java:382

  }

  @Memoized HttpCall.Factory http() {
    return new HttpCall.Factory(lazyHttpClient().get());
  }

  @Override public void close() {
    lazyHttpClient().close();
  }

  ElasticsearchStorage() {
  }

  static final BodyConverter<CheckResult> READ_STATUS = new BodyConverter<CheckResult>() {
    @Override public CheckResult convert(JsonParser parser, Supplier<String> contentString)
      throws IOException {
      JsonParser status = enterPath(parser, "status");
      if (status == null) {
        throw new IllegalArgumentException("Health status couldn't be read " + contentString.get());
      }
      if ("RED".equalsIgnoreCase(status.getText())) {
        return CheckResult.failed(new IllegalStateException("Health status is RED"));
      }
      return CheckResult.OK;
    }

    @Override public String toString() {
      return "ReadStatus";
    }
  };
}

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Read the body embedded in the exception message — it tells you exactly what the server returned.
  2. If it is an auth error body, fix ES_USERNAME/ES_CREDENTIALS or the API key configuration.
  3. If it is an index/permission error, grant the user monitor/cluster privileges or fix the index template bootstrap.
  4. If it is a proxy page, route Zipkin directly to the ES HTTP port or fix the proxy.

Example fix

# before
# Health status couldn't be read {"error":{"root_cause":[..."unauthorized"...]},"status":401}

# after: supply credentials
export ES_USERNAME=zipkin ES_PASSWORD=*****
# curl -u zipkin http://es:9200/_cluster/health/zipkin-span-* returns {"status":"green"}
Defensive patterns

Strategy: fallback

Validate before calling

// verify the health endpoint yields a 'status' field with the same credentials Zipkin uses
var body = curlOrHttpClient(esHost + "/_cluster/health/zipkin-span", credentials);
var status = parseJson(body).get("status");
if (status == null) throw new IllegalStateException("Unexpected health body: " + body);

Try / catch

CheckResult result = storage.check();
if (!result.ok()) {
  Throwable cause = result.error();
  if (cause instanceof IllegalArgumentException il
      && il.getMessage().startsWith("Health status couldn't be read")) {
    // the raw server body is embedded in the message — use it to diagnose auth/proxy issues
  }
}

Prevention

When it happens

Trigger: The health endpoint returns JSON without a 'status' key: authentication/authorization errors (401/403 bodies), index-not-found error envelopes, a proxy error page that happens to be JSON, or hitting a non-ES JSON service. RED status additionally produces 'Health status is RED' rather than this error.

Common situations: Missing or wrong credentials against a secured Elasticsearch (X-Pack/OpenSearch security); the health-check index name does not exist yet or is blocked; TLS termination returning an error payload; proxy rewriting responses.

Related errors


AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14). Data as JSON: /api/errors/18aebc90139e9890. Report an issue: GitHub.