Skip to content

Migrate from HLRC to elasticsearch-java 8.x client - #920

Open
Yeikel Santana (yeikel) wants to merge 8 commits into
confluentinc:masterfrom
yeikel:migrate-java-client
Open

Migrate from HLRC to elasticsearch-java 8.x client#920
Yeikel Santana (yeikel) wants to merge 8 commits into
confluentinc:masterfrom
yeikel:migrate-java-client

Conversation

@yeikel

@yeikel Yeikel Santana (yeikel) commented May 15, 2026

Copy link
Copy Markdown

Problem

The High Level REST Client (HLRC) is deprecated and was fully removed in Elasticsearch 9, blocking support for ES 9.x. The connector currently depends on elasticsearch-rest-high-level-client 7.x.

Fixes #847.

Breaking change: This migration drops support for Elasticsearch 7.x, which reached end of life on January 15, 2026. The minimum supported version is now Elasticsearch 8.x.

Solution

Replace HLRC with co.elastic.clients:elasticsearch-java 8.19.15.

Dependencies

  • Removed elasticsearch-rest-high-level-client and elasticsearch (HLRC artifacts)
  • Added co.elastic.clients:elasticsearch-java 8.19.15 and elasticsearch-rest-client 8.19.15
  • Upgraded Jackson 2.15.2 → 2.18.6; added log4j-bom 2.25.4 to align transitive log4j versions pulled in by the new client
  • Set Java 11 as the compiler release target (required by elasticsearch-java)
  • Bumped Testcontainers 1.16.3 → 1.21.4 to fix Docker Engine 29.x incompatibility

Core client

  • BulkProcessorBulkIngester<SinkRecordAndOffset>: offset state is carried as context on each bulk operation instead of being looked up in a side map
  • Added DispatchingTransport — dispatches async callbacks to a dedicated executor, breaking a lock-ordering deadlock between BulkIngester's internal condition and the NIO thread pool. Uses a separate retryScheduler thread pool to prevent a second deadlock where retry tasks queued on bulkScheduler would block behind BulkIngester's own flush tasks
  • Removed BulkIngester.backoffPolicy to give DispatchingTransport sole ownership of the retry budget; previously both layers retried independently, causing up to (maxRetries+1)² HTTP requests per document
  • Removed blocking close() from throwIfFailed() — the Connect framework calls task.stop() → client.close() immediately after put() throws, so the defensive close was redundant and caused up to 3-minute delays before a task transitioned to FAILED

Data layer

  • DataConverter.convertRecord(): returns BulkOperation instead of DocWriteRequest
  • Mapping.buildMapping(): returns Map<String, Object> instead of XContentBuilder
  • External versioning (VersionType.EXTERNAL) inlined per operation — no single-call equivalent in the new builder API

SSL startup scripts

  • Removed chmod 777 ${ES_DIR} — script runs as elasticsearch user which cannot chmod its own install directory
  • Replaced su - elasticsearch << EOF heredoc with exec /usr/local/bin/docker-entrypoint.sh eswrapper for correct signal forwarding
  • Replaced openssl pkcs12 with elasticsearch-certutil for PKCS12 client cert generation
  • PATH update for the bundled JDK is now conditional on keytool not already being present

Does this solution apply anywhere else?

  • yes
  • no

Test Strategy

  • Unit tests
  • Integration tests (full suite via Testcontainers against a real ES 8.x instance)
  • System tests
  • Manual tests

Release Plan

Merge to master. No backport needed this is a forward-only migration and is not backwards compatible with ES 7.x. A new major version (16.x) should be considered for this release.

Notes

  • Java target: the client supports Java 8 at runtime, but this could be a good opportunity to formally bump the minimum JVM requirement to 11 or 17 in a follow-up
  • The ci: add GitHub Actions workflow commit should be dropped before merging it was added only to run the test suite via GitHub Actions since Confluent's Semaphore CI is not accessible from this fork
  • The build: upgrade testcontainers commit can be extracted into a standalone PR if preferred

@yeikel
Yeikel Santana (yeikel) requested a review from a team as a code owner May 15, 2026 16:40
@confluent-cla-assistant

Copy link
Copy Markdown

🎉 All Contributor License Agreements have been signed. Ready to merge.
✅ yeikel
Please push an empty commit if you would like to re-run the checks to verify CLA status for all contributors.

Comment thread .github/workflows/ci.yml
@@ -0,0 +1,37 @@
name: CI

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed before we merge. This was added to help me test end to end rather than locally only

<!-- switch statements on types exceed maximum complexity -->
<suppress
checks="(CyclomaticComplexity)"
checks="(CyclomaticComplexity|NPathComplexity)"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal with the migration was to producer the smaller diff possible. I can definitely fix this but the diff will be much larger. I think that it makes sense to take this in a subsequent change

@yeikel
Yeikel Santana (yeikel) force-pushed the migrate-java-client branch 11 times, most recently from 45ad48c to e34f70d Compare May 15, 2026 22:04
@yeikel
Yeikel Santana (yeikel) force-pushed the migrate-java-client branch 10 times, most recently from 6157170 to 5bee901 Compare May 19, 2026 21:01
@yeikel
Yeikel Santana (yeikel) force-pushed the migrate-java-client branch 3 times, most recently from f4c2754 to fa12854 Compare May 29, 2026 03:45
Replace the deprecated High Level REST Client with the new
elasticsearch-java 8.x client.
…ntainer

throwIfFailed() was calling close() before propagating the error. close()
calls BulkIngester.close() with a flushTimeoutMs=3min timeout waiting for
in-flight requests to drain. With ES down, this blocked the task thread for
up to 3 minutes, preventing the task from transitioning to FAILED until after
the test's 2-minute await window expired.

The Connect framework calls task.stop() -> client.close() immediately after
put() throws, so the defensive close inside throwIfFailed() was redundant.
Removing it lets the task fail fast via the standard task lifecycle.
…eadlock

BulkIngester submits afterBulk callbacks to bulkScheduler via scheduler.submit().
Its single scheduler thread can block inside FnCondition.awaitUninterruptibly()
while waiting for a concurrency slot (requestsInFlightCount < maxConcurrentRequests).

When DispatchingTransport shared bulkScheduler for retry tasks (introduced in the
previous commit), that created a deadlock:

  1. bulkScheduler thread blocks in sendRequestCondition.awaitUninterruptibly()
     waiting for an in-flight slot to free up.
  2. Freeing a slot requires result.completeExceptionally() to fire, which requires
     the retry task to run.
  3. The retry task is queued in bulkScheduler, but bulkScheduler is blocked.
  4. Neither side can proceed — the connector task stays RUNNING indefinitely.

Fix: give DispatchingTransport its own dedicated single-threaded retryScheduler so
retry tasks are never blocked by BulkIngester's flush tasks.
BulkIngester.backoffPolicy and DispatchingTransport both used
config.maxRetries(), causing up to (maxRetries+1)^2 HTTP requests per
document: each BulkIngester per-item 429 retry issued a new bulk request
that re-entered DispatchingTransport with a fresh retry budget.

Removing backoffPolicy gives DispatchingTransport sole ownership of the
retry budget. HTTP-level 429s (whole-request) continue to be retried as
exceptions by DispatchingTransport; per-item 429s in a 200 response go
to the listener's error path (DLQ + task failure), matching pre-migration
HLRC behavior.
@yeikel
Yeikel Santana (yeikel) force-pushed the migrate-java-client branch 4 times, most recently from 30294de to 4c357d9 Compare June 1, 2026 01:42
@yeikel

Copy link
Copy Markdown
Author

Jainam Jain (@jjain1259) Is there anything you can do to help this one move forward?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Elasticsearch 9.0.0

1 participant