---
layout: docu
title: Run Queries
---
## Overview
Java applications send queries and read result sets through the familiar `Statement`, `PreparedStatement`, and `ResultSet` interfaces. This page covers sending queries and reading DuckDB's nested and composite types. For opening the `Connection` these run on, see [Define Connections]({% link docs/preview/clients/java/connecting.md %}).
## Sending Queries
DuckDB supports the standard JDBC methods to send queries and retrieve result sets. First a `Statement` object has to be created from the `Connection`, this object can then be used to send queries using `execute()` and `executeQuery()`. `execute()` is meant for queries where no results are expected like [`CREATE TABLE`]({% link docs/preview/sql/statements/create_table.md %}) or [`UPDATE`]({% link docs/preview/sql/statements/update.md %}) etc. and `executeQuery()` is meant to be used for queries that produce results (e.g., [`SELECT`]({% link docs/preview/sql/statements/select.md %})). Below are two examples. See also the JDBC [`Statement`](https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html) and [`ResultSet`](https://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html) documentations.
```java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
Connection conn = DriverManager.getConnection("jdbc:duckdb:");
// create a table
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE items (item VARCHAR, value DECIMAL(10, 2), count INTEGER)");
// insert two items into the table
stmt.execute("INSERT INTO items VALUES ('jeans', 20.0, 1), ('hammer', 42.2, 2)");
try (ResultSet rs = stmt.executeQuery("SELECT * FROM items")) {
while (rs.next()) {
System.out.println(rs.getString(1));
System.out.println(rs.getInt(3));
}
}
stmt.close();
```
```text
jeans
1
hammer
2
```
DuckDB also supports prepared statements as per the JDBC API:
```java
import java.sql.PreparedStatement;
try (PreparedStatement stmt = conn.prepareStatement("INSERT INTO items VALUES (?, ?, ?);")) {
stmt.setString(1, "chainsaw");
stmt.setDouble(2, 500.0);
stmt.setInt(3, 42);
stmt.execute();
// more calls to execute() possible
}
```
> Warning Do *not* use prepared statements to insert large amounts of data into DuckDB. See the [data import documentation]({% link docs/preview/data/overview.md %}) for better options.
## Reading Nested and Composite Types
DuckDB supports nested and composite column types such as `LIST`, `STRUCT`, `MAP`, `UNION`, and `ENUM`. The JDBC driver maps each of these to an idiomatic Java object. Read the values from a `ResultSet` with `getObject`, which returns the following types:
| DuckDB type | Java type returned by `getObject` |
|--|--|
| `LIST`, `ARRAY` | `org.duckdb.DuckDBArray` (implements `java.sql.Array`) |
| `STRUCT` | `org.duckdb.DuckDBStruct` (implements `java.sql.Struct`) |
| `MAP` | `java.util.LinkedHashMap` |
| `UNION` | the resolved member value |
| `ENUM` | `String` |
```java
try (ResultSet rs = stmt.executeQuery("SELECT [1, 2, 3] AS l, {'a': 1, 'b': 2} AS s")) {
while (rs.next()) {
DuckDBArray list = (DuckDBArray) rs.getObject(1);
Object[] values = (Object[]) list.getArray();
DuckDBStruct struct = (DuckDBStruct) rs.getObject(2);
Map fields = struct.getMap();
}
}
```
The DuckDB result set also exposes typed accessors as extensions to the JDBC API, including `getArray(int)`, `getStruct(int)`, `getUuid(int)`, `getHugeint(int)`, and `getJsonObject(int)`. `DuckDBStruct.getMap()` returns the struct fields keyed by name.
`DuckDBArray.getResultSet()` exposes the list elements as an `org.duckdb.DuckDBArrayResultSet`, a read-only `ResultSet` with two columns: `INDEX`, the 1-based position of the element, and `VALUE`, the element itself. Iterate it like any other result set:
```java
try (ResultSet rs = stmt.executeQuery("SELECT [10, 20, 30] AS l")) {
rs.next();
DuckDBArray list = (DuckDBArray) rs.getObject(1);
try (ResultSet elements = list.getResultSet()) {
while (elements.next()) {
int index = elements.getInt("INDEX"); // 1-based position
int value = elements.getInt("VALUE"); // the element
System.out.println(index + " -> " + value);
}
}
}
```
A `JSON` column is returned by `getObject(int)` (and `getJsonObject(int)`) as an `org.duckdb.JsonNode`, a lightweight wrapper over the raw JSON text. Test its type with `isArray()`, `isObject()`, `isString()`, `isNumber()`, `isBoolean()`, and `isNull()`, and recover the JSON source with `toString()`:
```java
try (ResultSet rs = stmt.executeQuery("SELECT '[1, 2, 3]'::JSON AS j")) {
rs.next();
JsonNode json = (JsonNode) rs.getObject(1);
if (json.isArray()) {
System.out.println(json); // [1, 2, 3]
}
}
```
## Binding Nested and Composite Parameters
To bind a `LIST`/`ARRAY`, `STRUCT`, or `MAP` value as a prepared-statement parameter, build it from the `Connection` and pass it to `setObject()`. Each factory method attaches the SQL type name the driver needs to marshal the value.
* `createArrayOf(typeName, elements)` — the standard JDBC method — returns an `org.duckdb.user.DuckDBUserArray` (a `java.sql.Array`) for a `LIST` or `ARRAY`, where `typeName` is the element type.
* `createStruct(typeName, attributes)` — the standard JDBC method — returns an `org.duckdb.user.DuckDBUserStruct` (a `java.sql.Struct`), where `typeName` is the `STRUCT` type and `attributes` are the field values in declaration order.
* `createMap(typeName, map)` — a DuckDB extension on `DuckDBConnection` — returns an `org.duckdb.user.DuckDBMap` (a `java.util.Map`) for a `MAP`, where `typeName` is the full map type such as `MAP(VARCHAR, INTEGER)`.
```java
import java.sql.Array;
import java.sql.Struct;
import java.util.Map;
import org.duckdb.DuckDBConnection;
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
// LIST / ARRAY
Array list = conn.createArrayOf("INTEGER", new Object[] {1, 2, 3});
try (PreparedStatement stmt = conn.prepareStatement("SELECT ?::INTEGER[]")) {
stmt.setObject(1, list);
stmt.execute();
}
// STRUCT
Struct point = conn.createStruct("STRUCT(x DOUBLE, y DOUBLE)", new Object[] {1.0, 2.0});
// MAP
Map counts = conn.createMap("MAP(VARCHAR, INTEGER)", Map.of("a", 1, "b", 2));
```
### Binding Spatial Values
The [`spatial` extension]({% link docs/preview/core_extensions/spatial/overview.md %}) builds its geometry types from `STRUCT` and `LIST` types, so they are bound with the same factory methods. A `POINT_2D` is a struct of two `DOUBLE` fields, and a `LINESTRING` is a list of such points. After loading the extension, build the value with `createStruct()` (or `createArrayOf()`) and cast the parameter to `GEOMETRY`; a `GEOMETRY` result comes back as Well-Known Binary through `getBlob()`:
```java
try (Statement stmt = conn.createStatement()) {
stmt.execute("INSTALL spatial");
stmt.execute("LOAD spatial");
}
// POINT_2D is a STRUCT(x DOUBLE, y DOUBLE)
Struct point = conn.createStruct("POINT_2D", new Object[] {41.1, 42.2});
try (PreparedStatement stmt = conn.prepareStatement("SELECT ?::POINT_2D::GEOMETRY")) {
stmt.setObject(1, point);
try (ResultSet rs = stmt.executeQuery()) {
rs.next();
Blob wkb = rs.getBlob(1); // the geometry as Well-Known Binary
}
}
// A LINESTRING is a LIST of POINT_2D structs
Struct p1 = conn.createStruct("POINT_2D", new Object[] {0.0, 0.0});
Struct p2 = conn.createStruct("POINT_2D", new Object[] {1.0, 1.0});
Array line = conn.createArrayOf("POINT_2D", new Object[] {p1, p2});
```
## Binding Temporal Parameters
Date and time parameters follow the same `setObject()` path. When you bind a `java.time` or `java.sql` temporal value, the driver wraps it in the matching internal holder — `java.sql.Date`/`LocalDate` become an `org.duckdb.DuckDBDate`, `Timestamp`/`LocalDateTime` become a `DuckDBTimestamp`, `OffsetDateTime` becomes a `DuckDBTimestampTZ`, and `Time`/`LocalTime` become a `DuckDBTime` — and marshals it to the corresponding DuckDB type:
```java
import java.time.LocalDate;
import java.time.LocalDateTime;
try (PreparedStatement stmt = conn.prepareStatement("SELECT ?, ?")) {
stmt.setObject(1, LocalDate.of(2024, 1, 15));
stmt.setObject(2, LocalDateTime.of(2024, 1, 15, 12, 30));
stmt.execute();
}
```
Each holder keeps its value relative to the Unix epoch (`1970-01-01`): `DuckDBDate` counts whole days through `getDaysSinceEpoch()`, while `DuckDBTimestamp`, `DuckDBTimestampTZ`, and `DuckDBTime` report microseconds through `getMicrosEpoch()` (for `DuckDBTime`, microseconds since midnight):
```java
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.OffsetDateTime;
import org.duckdb.DuckDBDate;
import org.duckdb.DuckDBTime;
import org.duckdb.DuckDBTimestamp;
import org.duckdb.DuckDBTimestampTZ;
long days = new DuckDBDate(Date.valueOf("2024-01-15")).getDaysSinceEpoch(); // 19737
long micros = new DuckDBTimestamp(Timestamp.valueOf("2024-01-15 12:30:00")).getMicrosEpoch();
long zonedMicros = new DuckDBTimestampTZ(OffsetDateTime.parse("2024-01-15T12:30:00Z")).getMicrosEpoch();
long timeMicros = new DuckDBTime(Time.valueOf("12:30:00")).getMicrosEpoch();
```
## Inspecting Metadata
The driver implements the standard JDBC metadata interfaces, so an application can introspect the database, a result set, or a prepared statement's parameters without running catalog queries by hand.
* `Connection.getMetaData()` returns an `org.duckdb.DuckDBDatabaseMetaData`, a `java.sql.DatabaseMetaData` that reports database-wide capabilities and exposes catalog objects such as `getTables()`, `getColumns()`, and `getSchemas()` as result sets built from DuckDB's system tables.
* `ResultSet.getMetaData()` returns an `org.duckdb.DuckDBResultSetMetaData` describing the result columns — `getColumnCount()`, `getColumnName(int)`, `getColumnType(int)`, `getColumnTypeName(int)`, `getPrecision(int)`, and `getScale(int)`.
* `PreparedStatement.getParameterMetaData()` returns an `org.duckdb.DuckDBParameterMetaData` describing the statement's parameters (1-based), including `getParameterCount()`, `getParameterType(int)`, and `getParameterTypeName(int)`.
```java
try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM items WHERE value > ?")) {
stmt.setDouble(1, 10.0);
ResultSetMetaData rsMeta = stmt.getMetaData();
for (int i = 1; i <= rsMeta.getColumnCount(); i++) {
System.out.println(rsMeta.getColumnName(i) + " : " + rsMeta.getColumnTypeName(i));
}
System.out.println("parameters: " + stmt.getParameterMetaData().getParameterCount());
}
```
As a DuckDB extension, `DuckDBResultSetMetaData.getReturnType()` reports what a statement returns as an `org.duckdb.StatementReturnType` — `QUERY_RESULT`, `CHANGED_ROWS`, or `NOTHING`:
```java
import org.duckdb.DuckDBResultSetMetaData;
import org.duckdb.StatementReturnType;
DuckDBResultSetMetaData meta = (DuckDBResultSetMetaData) rs.getMetaData();
StatementReturnType returnType = meta.getReturnType(); // QUERY_RESULT
```
For a `DECIMAL` column, `getPrecision(int)` and `getScale(int)` report the width and scale that DuckDB records for the type in a `DuckDBColumnTypeMetaData`. For the `value DECIMAL(10, 2)` column of the `items` table, they return `10` and `2`:
```java
try (ResultSet rs = stmt.executeQuery("SELECT value FROM items")) {
ResultSetMetaData rsMeta = rs.getMetaData();
int precision = rsMeta.getPrecision(1); // 10
int scale = rsMeta.getScale(1); // 2
System.out.println("DECIMAL(" + precision + ", " + scale + ")");
}
```
## Further Reading
* [Handle Results]({% link docs/preview/clients/java/result_handling.md %}) — Apache Arrow interchange, result streaming, and chunked results beyond the standard `ResultSet`.
* [Import Data]({% link docs/preview/clients/java/data_import.md %}) — the Appender and batch writer, the recommended alternatives to prepared statements for bulk inserts.
* [Prepared Statements]({% link docs/preview/sql/query_syntax/prepared_statements.md %}) — DuckDB's SQL-level support for the parameterized queries used here.
* [Define Connections]({% link docs/preview/clients/java/connecting.md %}) — opening the `Connection` that these statements run on.