Skip to content
Query.Farm
Talk with Us

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.

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
  • The method signature is the SQL signature. @Vector, @Const and @Setting on compute’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.