Skip to main content

Getting Started

A step-by-step introduction to Foundations JDBC. The library is MIT-licensed and open source.

Dependencies

Pick the one dependency for your language:

build.gradle.kts
implementation("dev.typr.foundations:foundations-jdbc:1.0.0-RC1")
pom.xml
<dependency>
<groupId>dev.typr.foundations</groupId>
<artifactId>foundations-jdbc</artifactId>
<version>1.0.0-RC1</version>
</dependency>

Each module includes everything you need — the Kotlin and Scala modules depend on the core transitively.

Imports

import dev.typr.foundations.*;           // Core: Fragment, RowCodec, OperationRead, Transactor, *Types
import dev.typr.foundations.connect.*; // Connection: ConnectionSource, *Config
import dev.typr.foundations.data.*; // Data types: Json, Range, Uint4, etc.

Setting up a connection

The quickest way to get started is with DuckDB in-memory. DuckDB is an embedded database that requires no Docker, no server, and no setup. Add the dependency and go:

val tx: Transactor =
ConnectionSource.of(DuckDbConfig.inMemory().build())
.transactor()

val result: Int = sql { "SELECT 42" }
.queryExactlyOne(DuckDbTypes.integer)
.transact(tx)

For production connection setup with PostgreSQL, MariaDB, and other databases, see Transactors.

Your first query

Define a record, a row codec that maps columns to fields, and a query that returns typed results:

data class City(val name: String, val population: Int)

val cityCodec: RowCodecNamed<City> =
RowCodec.namedBuilder<City>()
.field("name", DuckDbTypes.varchar, City::name)
.field("population", DuckDbTypes.integer, City::population)
.build(::City)

val findCities: OperationRead<List<City>> =
sql { "SELECT ${cityCodec.columnList} FROM city ORDER BY population DESC" }
.query(cityCodec.all())

The RowCodecNamed maps database columns to record fields by name. Use .all() to collect all rows, .exactlyOne() for a single result, or .maxOne() for an optional result. See Row Codecs for more.

Parameterized queries

Use your types and codecs in a plain function to build parameterized queries:

fun findCityByName(name: String): OperationRead<City?> =
sql { "SELECT ${cityCodec.columnList} FROM city WHERE name = ${DuckDbTypes.varchar(name)}" }
.query(cityCodec.maxOne())

Values are always bound as JDBC parameters — never concatenated into SQL strings.

Query analysis

Query Analysis catches type mismatches, nullability errors, and missing columns at test time:

Query analysis catches type mismatches at test time

What's next

Concepts Flow

Continue reading: FragmentsRow CodecsOperationsTransactorsQuery Analysis

Jump to a topic: Database Types (type catalog) · Dynamic Queries (optional filters & conditional clauses)

Full examples