Skip to main content

Introduction to Typr DB

Your database schema is a contract. Typr DB enforces it.

Typr DB generates type-safe code from your database schema—row types, ID wrappers, repositories, and a SQL DSL—for Java, Kotlin, and Scala. Six databases supported. One type system.

The Problem: The Database Boundary​

The compiler checks your application code. But at the database boundary, you're on your own:

  • Column names matched by strings that can silently break
  • ID types mixed up (UserId vs OrderId vs Long)
  • Nullable columns accessed without null checks
  • Schema changes that break code at runtime, not compile time
  • New team members who don't know which types go where

The result: Bugs that compile, ship, and break in production.

The Solution: Schema as Contract​

Typr DB treats your database schema as the source of truth:

  1. Read your schema from PostgreSQL, MariaDB, Oracle, SQL Server, DuckDB, or DB2
  2. Generate typed code for every table, view, and relationship
  3. Enforce at compile time that all database access uses the correct types

When you change a column, rename a table, or modify a relationship—the compiler shows every impact. No grep. No prayer. Just fix the compile errors.

Who Benefits​

New team members are productive on day one. The types guide them to correct usage. The compiler catches their mistakes before code review.

Contractors work within well-defined boundaries. They can't accidentally use the wrong ID type or forget a nullable check. The contract is explicit.

Seniors review schema changes—10 lines of migration—not 1000 lines of implementation. High-leverage review where it matters.

What Gets Generated​

ComponentDescription
Row typesImmutable data classes matching your table structure
ID typesUserId, OrderId—distinct types that can't be mixed up
RepositoriesType-safe CRUD operations for every table
SQL DSLCompose queries with full type checking
SQL file bindingsWrite .sql files, get typed methods
Test infrastructureMock repositories, test data generators

Supported Databases​

DatabaseStatus
PostgreSQLFull support
MariaDB / MySQLFull support
OracleFull support
SQL ServerFull support
DuckDBFull support
IBM DB2Full support

Supported Languages​

LanguageFeatures
Java 17+Records, sealed interfaces, modern idioms
KotlinValue classes, null safety, data classes
Scala 2.13 / 3.xCase classes, Option types, functional style

Example: What You Get​

From a simple table, Typr generates everything you need:

CompanyId
/** Type for the primary key of table `showcase.company` */
public record CompanyId(String value) {
public CompanyId withValue(String value) {
return new CompanyId(value);
}

@Override
public java.lang.String toString() {
return value.toString();
}

public static Bijection<CompanyId, String> bijection =
Bijection.of(CompanyId::value, CompanyId::new);

public static PgType<CompanyId> pgType =
PgTypes.text.to(Bijection.of(CompanyId::new, CompanyId::value));

public static PgType<List<CompanyId>> pgTypeArray = pgType.array();
}
CompanyRow
/** Table: showcase.company Primary key: id */
public record CompanyRow(
CompanyId id,
String name,
Optional<Integer> foundedYear,
/** Default: true */
Optional<Boolean> active,
/** Default: CURRENT_TIMESTAMP */
Optional<LocalDateTime> createdAt)
implements Tuple5<
CompanyId, String, Optional<Integer>, Optional<Boolean>, Optional<LocalDateTime>> {
CompanyRepo
public interface CompanyRepo {
DeleteBuilder<CompanyFields, CompanyRow> delete();

Boolean deleteById(CompanyId id, Connection c);

Integer deleteByIds(List<CompanyId> ids, Connection c);

CompanyRow insert(CompanyRow unsaved, Connection c);

CompanyRow insert(CompanyRowUnsaved unsaved, Connection c);

Long insertStreaming(Iterator<CompanyRow> unsaved, Integer batchSize, Connection c);

/** NOTE: this functionality requires PostgreSQL 16 or later! */
Long insertUnsavedStreaming(Iterator<CompanyRowUnsaved> unsaved, Integer batchSize, Connection c);

SelectBuilder<CompanyFields, CompanyRow> select();

List<CompanyRow> selectAll(ConnectionRead c);

Optional<CompanyRow> selectById(CompanyId id, ConnectionRead c);

List<CompanyRow> selectByIds(List<CompanyId> ids, ConnectionRead c);

Map<CompanyId, CompanyRow> selectByIdsTracked(List<CompanyId> ids, ConnectionRead c);

UpdateBuilder<CompanyFields, CompanyRow> update();

Boolean update(CompanyRow row, Connection c);

CompanyRow upsert(CompanyRow unsaved, Connection c);

List<CompanyRow> upsertBatch(Iterator<CompanyRow> unsaved, Connection c);

/** NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */
Integer upsertStreaming(Iterator<CompanyRow> unsaved, Integer batchSize, Connection c);
}

Video Demo​

Write your SQL in .sql files. Typr regenerates correct mapping code on save.

Types of Database Interactions​

  1. CRUD Operations: Repository methods for simple and safe CRUD, plus the SQL DSL for batch operations.
  2. Simple Reads: Joins and filters using the SQL DSL.
  3. Complex Reads: Aggregations, window functions, CTEs—handled by writing SQL files.
  4. Dynamic Queries: For truly dynamic queries, Typr integrates with your existing database library.

Ready to make your database boundary type-safe? Keep reading to discover what Typr DB can do.