Skip to content

Querying and result mapping

JdbcClient.sql(...) returns a SQL specification. It supports named parameters such as :name, positional parameters such as ?, statement hints, typed mapping, and update statements.

Retrieving simple values

Use singleValue(...) or optionalValue(...) to read a single scalar result from the database.

Long count = client.sql("SELECT COUNT(*) FROM engineers")
        .singleValue(Long.class);

Optional<Long> maybeCount = client.sql("SELECT COUNT(*) FROM engineers")
        .optionalValue(Long.class);

Mapping results to records and POJOs

The built-in typed mapper matches result-column labels to record components or fields. Underscores are ignored when matching a column name to a record component.

public record DevSummary(Long id, String devName) {
}

List<DevSummary> devs = client
        .sql("SELECT id, dev_name FROM engineers ORDER BY id")
        .query(DevSummary.class)
        .list();

The mapper also works with a no-argument POJO whose fields match the result columns.

The following result-access patterns are available:

  • single() requires exactly one row.
  • optional() allows zero or one row.
  • list() reads all rows before returning.
  • stream() returns a lazy stream, and the stream should be closed when work is complete so JDBC resources are released.
DevSummary one = client.sql("SELECT ... WHERE id = :id")
        .param("id", 1L)
        .query(DevSummary.class)
        .single();

Optional<DevSummary> maybeOne = client.sql("SELECT ... WHERE id = :id")
        .param("id", 1L)
        .query(DevSummary.class)
        .optional();

Stream<DevSummary> stream = client.sql("SELECT ...")
        .query(DevSummary.class)
        .stream();

Customizing row mapping

Mapping can be defined inline with a lambda or reused through a dedicated RowMapper.

List<String> names = client
        .sql("SELECT dev_name FROM engineers ORDER BY id")
        .query((rs, rowNum) -> rs.getString("dev_name"))
        .list();

RowMapper<DevSummary> mapper = (rs, rowNum) ->
        new DevSummary(rs.getLong("id"), rs.getString("dev_name"));

List<DevSummary> devs = client
        .sql("SELECT id, dev_name FROM engineers")
        .query(mapper)
        .list();

Configuring converters

Converters translate a database value into the Java type requested by a typed mapper or scalar query. The built-in mapper supports common conversions for numeric types, strings, booleans, SQL date/time types, and Java time types.

Use a ConverterRegistry with a standalone client:

ConverterRegistry registry = new ConverterRegistry()
        .register(UUID.class, String.class, UUID::toString)
        .register(String.class, UUID.class, UUID::fromString);

JdbcClient client = JdbcClient.builder(dataSource)
        .converters(registry)
        .build();

A converter implements Converter<S, T>:

Converter<UUID, String> converter = UUID::toString;
registry.register(UUID.class, String.class, converter);

In a CDI application, register the converter as an application-scoped bean. The CDI integration automatically discovers the generic source and target types.

@ApplicationScoped
public class UuidToStringConverter implements Converter<UUID, String> {

    @Override
    public String convert(UUID source) {
        return source.toString();
    }
}

Converters are consulted when the JDBC value is not already assignable to the requested target type.

For executing inserts, updates, and batch statements, see updates. For configuration options, see configuration.