{"record":{"id":"2ec2f77595e3316d","repo":"SonarSource/sonarqube","slug":"at-least-one-elasticsearch-host-is-required","errorCode":null,"errorMessage":"At least one Elasticsearch host is required","messagePattern":"At least one Elasticsearch host is required","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/sonar-server-common/src/main/java/org/sonar/server/es/SynchronousEsHttpClient.java","lineNumber":77,"sourceCode":" * call submits the request to an I/O reactor thread and blocks the caller on a future. That extra thread\n * hand-off is nearly free on an idle host but costs milliseconds of scheduling latency per request on a\n * CPU-contended host, which - multiplied over the many small bulk/refresh/search round-trips of an\n * analysis - noticeably slows indexing (see SONAR-30530). This client instead performs the request inline\n * on the calling thread, with no hand-off.\n * <p>\n * Requests are round-robined across the configured hosts. On a connection-establishment failure the next\n * host is tried; other failures (including responses received from a node) are propagated without retry, so\n * a non-idempotent request such as a bulk is never re-sent after it reached a node.\n */\npublic final class SynchronousEsHttpClient implements TransportHttpClient {\n\n  private final CloseableHttpClient httpClient;\n  private final List<HttpHost> hosts;\n  private final AtomicInteger nextHost = new AtomicInteger(0);\n\n  public SynchronousEsHttpClient(CloseableHttpClient httpClient, List<HttpHost> hosts) {\n    if (hosts.isEmpty()) {\n      throw new IllegalArgumentException(\"At least one Elasticsearch host is required\");\n    }\n    this.httpClient = httpClient;\n    this.hosts = List.copyOf(hosts);\n  }\n\n  /**\n   * The configured Elasticsearch hosts, in the order they were provided.\n   */\n  public List<HttpHost> hosts() {\n    return hosts;\n  }\n\n  @Override\n  public Response performRequest(String endpointId, @Nullable Node node, Request request, TransportOptions options) throws IOException {\n    Map<String, String> headers = request.headers();\n    ContentType contentType = null;\n    String ct = headers.get(HeaderMap.CONTENT_TYPE);\n    if (ct != null) {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-server-common/src/main/java/org/sonar/server/es/SynchronousEsHttpClient.java#L59-L95","documentation":"SynchronousEsHttpClient's constructor requires a non-empty list of Elasticsearch HttpHosts; an empty list means there is nowhere to send requests, so it throws IllegalArgumentException immediately. This is a guard against a misconfigured ES client with zero endpoints.","triggerScenarios":"Constructing SynchronousEsHttpClient with an empty hosts list, which occurs when no sonar.search.hosts entries are configured (properties missing or all filtered out).","commonSituations":"sonar.search.hosts not set in sonar.properties; configuration binding stripping empty values; cluster bootstrap ordering where ES config is absent.","solutions":["Configure at least one Elasticsearch host (sonar.search.hosts or default localhost:9001) before the client is built.","Check the configuration source: ensure the property key is spelled correctly and loaded (correct file/environment).","If hosts come from clustering config, verify the cluster node settings are present.","If constructing programmatically, assert the list is non-empty before calling the constructor."],"exampleFix":"// before\nnew SynchronousEsHttpClient(httpClient, Collections.emptyList());\n// after\nnew SynchronousEsHttpClient(httpClient, List.of(new HttpHost(\"http\", address, 9001)));","handlingStrategy":"validation","validationCode":"if (hosts == null || hosts.isEmpty()) { throw new IllegalStateException(\"Configure sonar.search.hosts before creating SynchronousEsHttpClient\"); }","typeGuard":null,"tryCatchPattern":"try { client = new SynchronousEsHttpClient(httpClient, hosts); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"At least one Elasticsearch host\")) { log.error(\"No ES hosts configured\"); } throw e; }","preventionTips":["Always configure at least one ES host (default localhost:9001 exists for a reason)","Assert config lists are non-empty during startup wiring","Add a startup health check that pings the first host"],"tags":["elasticsearch","configuration","empty-list"],"backgroundTag":"missing-required-config-field","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}