In-process columnar access to Elasticsearch — direct Arrow format, no separate server required.
- In-Process — No separate server required, runs entirely within your JVM
- Columnar Native — Direct Arrow format, ideal for analytics and data engineering
- Lazy Streaming — Memory-efficient row consumption via
CloseableRowIterator; only the current batch resides in memory - ServiceLoader Discovery — Automatic driver registration via
AdbcDriverManager - Bulk Ingest — Arrow-to-SQL bulk data loading
- Full SQL — DDL + DML + DQL
Download the self-contained fat JAR for your Elasticsearch version:
| Elasticsearch | Artifact |
|---|---|
| ES 6.x | softclient4es6-adbc-driver-0.3.3.jar |
| ES 7.x | softclient4es7-adbc-driver-0.3.3.jar |
| ES 8.x | softclient4es8-adbc-driver-0.3.3.jar |
| ES 9.x | softclient4es9-adbc-driver-0.3.3.jar |
Maven:
<dependency>
<groupId>app.softnetwork.elastic</groupId>
<artifactId>softclient4es8-adbc-driver</artifactId>
<version>0.3.3</version>
</dependency>Gradle:
implementation 'app.softnetwork.elastic:softclient4es8-adbc-driver:0.3.3'sbt:
libraryDependencies += "app.softnetwork.elastic" % "softclient4es8-adbc-driver" % "0.3.3"import org.apache.arrow.adbc.core._
import org.apache.arrow.adbc.drivermanager.AdbcDriverManager
import org.apache.arrow.memory.RootAllocator
val allocator = new RootAllocator()
val params = new java.util.HashMap[String, AnyRef]()
params.put(AdbcDriver.PARAM_URI.getKey,
"adbc:elastic://localhost:9200?user=elastic&password=changeme")
val db = AdbcDriverManager.getInstance().connect(params, allocator)
val conn = db.connect()
val stmt = conn.createStatement()
stmt.setSqlQuery("SELECT * FROM my_index LIMIT 10")
val result = stmt.executeQuery()
val reader = result.getReader
while (reader.loadNextBatch()) {
val root = reader.getVectorSchemaRoot
// Process Arrow columnar data...
}
reader.close(); stmt.close(); conn.close(); db.close(); allocator.close()# Username / password
adbc:elastic://<host>:<port>?user=<user>&password=<password>
# API key
adbc:elastic://<host>:<port>?api-key=<api-key>
# Bearer token
adbc:elastic://<host>:<port>?bearer=<token>
Elasticsearch SQL can't JOIN across indices — SoftClient4ES does. Free in Community: up to 2 cross-index JOINs per query (a 3-table JOIN). ADBC routes the JOIN through the same embedded DuckDB engine as the JDBC driver, so the SQL is identical — only the connection setup differs:
SELECT e.name, e.salary, d.dept_name
FROM jdbc_join_emp e
JOIN jdbc_join_dept d ON e.dept_id = d.dept_id;
-- 5 rows (the orphan employee with dept_id = 99 is dropped by the INNER JOIN)Set this as the statement's SQL (stmt.setSqlQuery(...)) and read the Arrow stream as shown in Usage. For the full JOIN matrix, see the Cross-Index JOIN walkthrough (../sql/joins.md).
The ADBC driver is Arrow-native in process, and Apache Arrow reaches into java.nio to address off-heap memory. JDK 16 and later deny that access by default (JEP 396), so a modern JVM must be started with:
--add-opens=java.base/java.nio=ALL-UNNAMED
Unlike the JDBC driver — where only the cross-index JOIN goes through Arrow — ADBC hands back Arrow batches for every query, so the flag is a prerequisite for the driver as a whole, not for one feature.
Below JDK 16 no flag is needed, but the floor is JDK 11: the Arrow release bundled here is compiled for Java 11.
Without the flag, Arrow's allocator constructs successfully and then fails on the first vector it fills, reporting:
Failed to initialize MemoryUtil. You must start Java with
`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`
(See https://arrow.apache.org/docs/java/install.html)
Arrow's own message names its module first, which matters when Arrow is on the module path. On a classpath deployment — which is how these fat JARs ship — --add-opens=java.base/java.nio=ALL-UNNAMED alone is sufficient; both forms work.
In a JVM you do not launch yourself (an application server, a notebook kernel, a BI tool) the same flag can be supplied through the JAVA_TOOL_OPTIONS environment variable, or through whatever mechanism that host provides for its own JVM arguments. The per-tool procedure has not yet been verified for any specific host, so this page does not print one.
A JAR manifest cannot do this for you: the JVM honours a manifest's
Add-Opensattribute only for the jar named on ajava -jarlaunch — never for a jar loaded from an application's or a tool's classpath.
adbc.elastic {
batch-size = 1000 # env: ADBC_BATCH_SIZE
query-timeout-seconds = 120 # env: ADBC_QUERY_TIMEOUT
}
elastic.credentials {
host = "localhost" # env: ELASTIC_HOST
port = 9200 # env: ELASTIC_PORT
# Choose one authentication method:
username = "elastic" # env: ELASTIC_CREDENTIALS_USERNAME (username/password)
password = "changeme" # env: ELASTIC_CREDENTIALS_PASSWORD
# api-key = "" # env: ELASTIC_CREDENTIALS_API_KEY (API key)
# bearer-token = "" # env: ELASTIC_CREDENTIALS_BEARER_TOKEN (bearer token)
}| Feature | JDBC | ADBC | Arrow Flight SQL |
|---|---|---|---|
| Process model | In-process | In-process | Separate server (gRPC) |
| Data format | Row-based (ResultSet) | Columnar (Arrow) | Columnar (Arrow) |
| Protocol | JDBC API | ADBC API | gRPC (HTTP/2) |
| Use case | Java apps, BI tools | Analytics, data engineering | Multi-client, networked |
| Setup | JAR on classpath | JAR on classpath | Docker/server deployment |
| Driver | Scala | ES versions | Clients | Process model |
|---|---|---|---|---|
| ADBC | cross-built Scala 2.12 + 2.13 | ES 6.x / 7.x / 8.x / 9.x | Java/JVM only (polyglot → Flight SQL) | In-process |
The fat JARs are Scala-version-independent for consumers — they bundle their own Scala runtime, so you almost never need to think about the Scala axis. For non-JVM languages (Python, Go, DuckDB, C++), use the Arrow Flight SQL server with the Arrow project's standard adbc_driver_flightsql client.
All client drivers (JDBC, ADBC, and the REPL) plus the Arrow Flight SQL sidecar are free in Community, including up to 2 cross-index JOINs per query (a 3-table JOIN). A 4-table JOIN (a 3rd cross-index JOIN in one query) is rejected by the planner with a message ending … Upgrade to Pro … See: https://portal.softclient4es.com/pricing.
Multi-cluster federation — joining across separate ES clusters — is Pro+ (maxClusters 1 / 5 / ∞). This quickstart covers the single-cluster shape, which is free.
Subqueries (IN (SELECT …), EXISTS, scalar, derived tables) and CTEs (WITH) are not supported in the current release — they arrive in a later release. Write the JOIN explicitly instead. See the Known Limitations & Roadmap (../sql/known_limitations.md) for the full list.
- JDBC Driver — the row-based in-process alternative for any JDBC tool.
- Arrow Flight SQL — the gRPC server for polyglot and networked clients.
- Cross-Index JOIN walkthrough (
../sql/joins.md) — the full JOIN matrix (rows 1/2/3) with worked examples. - Multi-cluster federation operator guide (
federation_operator_guide.md) — the Pro+ path: JOIN across separate ES clusters. - Known Limitations & Roadmap (
../sql/known_limitations.md) — what works in the current release vs what's coming.
The ADBC driver sends one anonymous usage ping per day (no IP, no SQL, no PII). Opt out with softclient4es.telemetry.enabled = false in your HOCON config, the SOFTCLIENT4ES_TELEMETRY_ENABLED=false environment variable, or -Dsoftclient4es.telemetry.enabled=false. ADBC has no connection-string opt-out — the adbc:elastic:// URI carries no telemetry option. See Telemetry & Privacy for details.
The ADBC driver is licensed under the Elastic License 2.0 — free to use, not open source.