⌘K ctrl+k
Search Shortcut cmd + k | ctrl + k
Handle Results

Overview

Beyond the standard JDBC ResultSet, the DuckDB driver can hand results to Apache Arrow, stream large results instead of materializing them, and expose raw columnar data chunks. This page covers each of these result-handling options.

Arrow Methods

The DuckDB result set integrates with Apache Arrow through the Java Arrow bindings. A DuckDBResultSet can export its rows as an Arrow stream for consumption by Arrow-based tools, and an Arrow stream produced elsewhere can be registered on a connection and queried as a DuckDB table. Refer to the API Reference for the type signatures.

Arrow Export

The following example exports an Arrow stream from a query result and consumes it using the Java Arrow bindings. Each batch is loaded in turn, and its vectors are read from the VectorSchemaRoot.

import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.duckdb.DuckDBResultSet;

try (var conn = DriverManager.getConnection("jdbc:duckdb:");
    var stmt = conn.prepareStatement("SELECT * FROM generate_series(2000)");
    var resultset = (DuckDBResultSet) stmt.executeQuery();
    var allocator = new RootAllocator()) {
    try (var reader = (ArrowReader) resultset.arrowExportStream(allocator, 256)) {
        while (reader.loadNextBatch()) {
            System.out.println(reader.getVectorSchemaRoot().getVector("generate_series"));
        }
    }
}

Arrow Import

The following example consumes an Arrow stream produced by the Java Arrow bindings and registers it on a DuckDB connection with registerArrowStream(). Once registered, the stream is addressed by the name passed to that method and can be queried like any other table.

import org.apache.arrow.c.ArrowArrayStream;
import org.apache.arrow.c.Data;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.ipc.ArrowStreamReader;
import org.duckdb.DuckDBConnection;
import org.duckdb.DuckDBResultSet;

// Arrow binding
try (var allocator = new RootAllocator();
     ArrowStreamReader reader = null; // should not be null of course
     var arrow_array_stream = ArrowArrayStream.allocateNew(allocator)) {
    Data.exportArrayStream(allocator, reader, arrow_array_stream);

    // DuckDB setup
    try (var conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:")) {
        conn.registerArrowStream("asdf", arrow_array_stream);

        // run a query
        try (var stmt = conn.createStatement();
             var rs = (DuckDBResultSet) stmt.executeQuery("SELECT count(*) FROM asdf")) {
            while (rs.next()) {
                System.out.println(rs.getInt(1));
            }
        }
    }
}

Streaming Results

Result streaming is opt-in in the JDBC driver. Enable it by setting the jdbc_stream_results config to true before running a query. The easiest way to do that is to pass it in the Properties object.

Properties props = new Properties();
props.setProperty(DuckDBDriver.JDBC_STREAM_RESULTS, String.valueOf(true));

Connection conn = DriverManager.getConnection("jdbc:duckdb:", props);

Chunked Results

The JDBC driver can return a query result as a lazily fetched sequence of columnar data chunks via the org.duckdb.DuckDBChunkedResult class. This exposes the C API's duckdb_fetch_chunk function to Java and avoids the per-row overhead required by the JDBC ResultSet interface. Chunk contents are read through the same DuckDBDataChunkReader API that is used by user-defined functions.

Call query() on a DuckDBPreparedStatement to obtain a DuckDBChunkedResult, then advance through the chunks with nextChunk() and read each column vector by index.

Example:

import java.sql.DriverManager;
import org.duckdb.DuckDBConnection;
import org.duckdb.DuckDBPreparedStatement;
import org.duckdb.DuckDBChunkedResult;
import org.duckdb.DuckDBDataChunkReader;
import org.duckdb.DuckDBReadableVector;

try (DuckDBConnection conn = DriverManager
        .getConnection("jdbc:duckdb:")
        .unwrap(DuckDBConnection.class);
     DuckDBPreparedStatement ps = conn.prepare("SELECT ? AS col1")) {

    // statement parameters are 1-based
    ps.setInt(1, 42);

    try (DuckDBChunkedResult res = ps.query()) {

        // advance to the next chunk, returns true on success
        while (res.nextChunk()) {

            // get the current chunk from the result
            DuckDBDataChunkReader chunk = res.chunk();

            // iterate over the chunk columns, all indices are 0-based
            for (long col = 0; col < chunk.columnCount(); col++) {

                // get a vector for the specified column
                DuckDBReadableVector vector = chunk.vector(col);

                // iterate over the vector rows
                for (long row = 0; row < chunk.rowCount(); row++) {
                    int val = vector.getInt(row);
                    System.out.println(val);
                }
            }
        }
    }
}

Statement parameters remain 1-based, following the JDBC convention, while chunk columns and rows are 0-based, matching the C API and the user-defined function interfaces.

The chunked result API currently supports basic data types only. Composite types such as LIST and STRUCT are not yet readable through this interface. The query() method is available on prepared statements only; there is no query(String) overload.

Further Reading

  • Run Queries — sending the queries whose results this page reads, and the standard ResultSet accessors.
  • Define Functions — the DuckDBDataChunkReader API shared with chunked results is also used to write user-defined functions.
  • Define Connections — the jdbc_stream_results option that enables result streaming.
  • C API — the duckdb_fetch_chunk function that the chunked result API exposes to Java.
© 2026 DuckDB Foundation, Amsterdam NL
DuckDB Home Code of Conduct Trademark Use Blog