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.27.0")
Requires JDK 25+. 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”
Tutorial
Zero to a scalar and a table function callable from DuckDB, in about 20 minutes.
Concepts
What VGI is, the worker model, and the Arrow data path — shared across every SDK.
API reference
The whole artifact — function shapes, worker, catalogs, client — generated from the sources.
Function lifecycle
When bind, init, process, combine and finalize each run, and how they map to DuckDB.
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. Large Arrow batches can 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, TypeScript, or C# one — the same DuckDB extension drives all of them.
Reference
Section titled “Reference”
Package overview
Orientation for the artifact, plus the map of every reference page.
Argument serialization
The Arrow schema every SDK produces for a function signature — the shared wire format.