Vector Gateway Interface for Java

Extend DuckDB with functions written in Java — backed by Apache Arrow, callable straight from SQL.
farm.query:vgi implements the Vector Gateway Interface (VGI) for Java: you write a small worker
exposing typed functions, and DuckDB calls them as if they were native. Data moves as Apache Arrow
record batches, so it stays columnar across the boundary — no row-by-row marshalling.
Install
Section titled “Install”implementation("farm.query:vgi:0.26.1")
Requires JDK 21+ (JDK 22+ unlocks the shared-memory transport). Two JVM flags and the
-parameters compiler flag are part of the contract — see
Running a worker.
A scalar function is a class with one method:
public final class ScalarExample extends ScalarFn {
@Override public String name() { return "upper_case"; }
// @Vector marks a per-row input column; the last unannotated vector is the
// framework-allocated output.
public void compute(@Vector VarCharVector value, VarCharVector result) {
result.allocateNew();
for (int i = 0; i < value.getValueCount(); i++) {
if (value.isNull(i)) { result.setNull(i); continue; }
byte[] up = new String(value.get(i), UTF_8).toUpperCase(Locale.ROOT).getBytes(UTF_8);
result.setSafe(i, up, 0, up.length);
}
}
public static void main(String[] args) {
Worker.builder()
.catalogName("demo")
.registerScalar(new ScalarExample())
.runFromArgs(args);
}
}
ATTACH 'demo' (TYPE vgi, LOCATION 'launch:/abs/path/bin/demo');
SELECT demo.upper_case('hello'); -- HELLO
Start here
Section titled “Start here”Three things to know up front
Section titled “Three things to know up front”- The method signature is the SQL signature.
@Vector,@Constand@Settingoncompute’s parameters generate the spec, the bind-time validation and the dispatch. There is no separate declaration to keep in sync — and no schema boilerplate. - How you run it matters as much as what you write. A cold JVM costs seconds, so
launch:(one reused worker) rather than a bare path is the difference between usable and not. - It can skip the pipe entirely. On JDK 22+, large Arrow batches pass through a POSIX shared-memory segment both processes map, negotiated at the transport layer with no code change in your worker.
Coming from another SDK? The protocol is identical and a Java worker is wire-compatible with a Python, Go, Rust or TypeScript one — the same DuckDB extension drives all of them.