
PostgreSQL driver, query-focused data-access layer and migrator; speaks Wire Protocol v3.2 directly, supports COPY, LISTEN/NOTIFY, large objects, session scoping and query builders.
Not an ORM. A ROME — a Relational-Object Mapping Engine. Because all queries lead to ROME.
Everything Octavius has to say about talking to PostgreSQL from Kotlin: a driver that speaks the wire protocol directly, a data access layer built on it, and a migrator that keeps the schema up to date.
They are separate artifacts on purpose. The driver is complete on its own — a connection pool and the driver are a working stack, and plenty of applications need nothing else. The client is for the layer above: somewhere to put a query that does not want a session threaded through its signature. Migrations answers a different question again, and takes the driver alone — never the client.
Augustus kept every form of the Republic and quietly took the power out of them. Octavius keeps every form of
JDBC — a Connection, a DataSource, a pool that recognises both — and quietly does none of what JDBC does
underneath.
| Principle | What it means here |
|---|---|
| Query is Imperator | Your SQL dictates the shape of the result. Nothing rewrites it, reorders it, or issues a query you did not write. |
| Object is a Vessel | A data class is a typed container for what came back — no proxies, no lazy loading, nothing dirty to check. |
| No Legate Speaks for It | The driver talks Wire Protocol v3.2 itself. Nothing is wrapped, and no other driver is delegated to underneath. |
| Each Province Governs Itself | Driver, client and migrations are separate artifacts and separate decisions. The driver alone is a working stack. |
| One Standard, No Fallback | v3.2 or nothing: PostgreSQL 17 fails the handshake rather than half-working, so there are no compatibility shims to carry. |
| Artifact | What it is |
|---|---|
driver — the road
|
The core. Wire Protocol v3.2 spoken directly, a type system read from your catalog, COPY, LISTEN/NOTIFY, Large Objects, TLS. README
|
client — the praetor
|
Session scoping, thread-bound transactions, query builders, transaction plans, dynamic_dto. README
|
client-scanner — the census
|
Finds the annotated classes in your packages and registers them, so thirty types are named once instead of thirty times. README |
migrations — the surveyor
|
A migrator on the driver: V/R naming, .sql files and Kotlin classes, checksums, an advisory lock, and a history table it keeps itself. README
|
pg-model — the codex
|
Multiplatform: the annotations Octavius reads off your own classes, a BigDecimal commonMain can name, and the serializers that keep it and PostgreSQL's infinity intact through JSON. |
driver-spring-integration — the treaty
|
OctaviusTemplate, exception translation, Spring Boot autoconfiguration. |
The road carries the query, the praetor decides which court hears it, the census enrols the citizens so that none has to present itself by name, the surveyor keeps the boundary stones and the record of who moved them, the codex is the text every province reads, and the treaty sets the terms a foreign power works under.
Not published: hikari-integration-tests (integration tests against a real pool), benchmarks (JMH against
pgjdbc), and examples/spring-app (a runnable sample, its own build pulling the driver in through
includeBuild).
The driver alone where something else already decides how connections and transactions are handled — a
Spring application, or code that is happy to open a session and use it. You write
dataSource.getOctaviusSession() and everything below that line is the driver's.
The client as well where that decision is yours to make. It answers one question the driver deliberately
leaves open — which session does this operation run on — so that a repository function can open a scope
without knowing whether it is already inside a transaction, and be right either way. The query builders and
dynamic_dto come with it.
Migrations where keeping this database's schema up to date is this application's job rather than
somebody else's. It sits beside the client rather than under it, needing only the driver, so it goes with the
client, without it, or not at all. OctaviusMigrator(dataSource).migrate() at startup is the whole of it.
Adding the client later costs nothing: it wraps no query and renames no method, so driver code keeps working unchanged next to it.
inline with a
reified parameter, and a reified function can only be inlined into Kotlin, never called. The non-reified
layer underneath is reachable from Java, but it means hand-building a KType for every column you read:
possible, not usable.driver-spring-integration only. The core driver has no Spring dependency at all.Pre-1.0 and written by one person. Every push runs the suite against a real PostgreSQL 18, alongside a job that generates certificates and exercises the TLS modes end to end, and a third that points the driver at PostgreSQL 17 to prove the handshake refuses it rather than half-working.
The API is not frozen. The driver is the settled part of it; the client and migrations are early. Every module carries the same version and is released together, so the number says when something shipped rather than how mature it is. None of it has seen long production use.
Every change is recorded in the CHANGELOG, grouped by module under each version — one file, because one version covers all of them and a release where only half the repository moved reads oddly split across two.
API Reference — generated KDoc for every
declaration, rebuilt on each push to master. Reach for it when you need a signature, a property, or the
values of an enum.
The guides cover what a signature cannot show — how the pieces behave together. There are three sets, because the driver stands on its own and the client and migrations are each optional beside it: the documentation index points at all three, and the driver's Quickstart is where to start from an empty project.
octavius-database is the previous generation and is
superseded by what is here. Much of it existed to work around pgjdbc — text-protocol composites, enums the
library had to be taught, a stateful ResultSet, no named parameters — and the driver answers all of that
natively. What was left over is the client, which is deliberately a much smaller thing than a port would have
been.
Licensed under the Apache License, Version 2.0.
Not an ORM. A ROME — a Relational-Object Mapping Engine. Because all queries lead to ROME.
Everything Octavius has to say about talking to PostgreSQL from Kotlin: a driver that speaks the wire protocol directly, a data access layer built on it, and a migrator that keeps the schema up to date.
They are separate artifacts on purpose. The driver is complete on its own — a connection pool and the driver are a working stack, and plenty of applications need nothing else. The client is for the layer above: somewhere to put a query that does not want a session threaded through its signature. Migrations answers a different question again, and takes the driver alone — never the client.
Augustus kept every form of the Republic and quietly took the power out of them. Octavius keeps every form of
JDBC — a Connection, a DataSource, a pool that recognises both — and quietly does none of what JDBC does
underneath.
| Principle | What it means here |
|---|---|
| Query is Imperator | Your SQL dictates the shape of the result. Nothing rewrites it, reorders it, or issues a query you did not write. |
| Object is a Vessel | A data class is a typed container for what came back — no proxies, no lazy loading, nothing dirty to check. |
| No Legate Speaks for It | The driver talks Wire Protocol v3.2 itself. Nothing is wrapped, and no other driver is delegated to underneath. |
| Each Province Governs Itself | Driver, client and migrations are separate artifacts and separate decisions. The driver alone is a working stack. |
| One Standard, No Fallback | v3.2 or nothing: PostgreSQL 17 fails the handshake rather than half-working, so there are no compatibility shims to carry. |
| Artifact | What it is |
|---|---|
driver — the road
|
The core. Wire Protocol v3.2 spoken directly, a type system read from your catalog, COPY, LISTEN/NOTIFY, Large Objects, TLS. README
|
client — the praetor
|
Session scoping, thread-bound transactions, query builders, transaction plans, dynamic_dto. README
|
client-scanner — the census
|
Finds the annotated classes in your packages and registers them, so thirty types are named once instead of thirty times. README |
migrations — the surveyor
|
A migrator on the driver: V/R naming, .sql files and Kotlin classes, checksums, an advisory lock, and a history table it keeps itself. README
|
pg-model — the codex
|
Multiplatform: the annotations Octavius reads off your own classes, a BigDecimal commonMain can name, and the serializers that keep it and PostgreSQL's infinity intact through JSON. |
driver-spring-integration — the treaty
|
OctaviusTemplate, exception translation, Spring Boot autoconfiguration. |
The road carries the query, the praetor decides which court hears it, the census enrols the citizens so that none has to present itself by name, the surveyor keeps the boundary stones and the record of who moved them, the codex is the text every province reads, and the treaty sets the terms a foreign power works under.
Not published: hikari-integration-tests (integration tests against a real pool), benchmarks (JMH against
pgjdbc), and examples/spring-app (a runnable sample, its own build pulling the driver in through
includeBuild).
The driver alone where something else already decides how connections and transactions are handled — a
Spring application, or code that is happy to open a session and use it. You write
dataSource.getOctaviusSession() and everything below that line is the driver's.
The client as well where that decision is yours to make. It answers one question the driver deliberately
leaves open — which session does this operation run on — so that a repository function can open a scope
without knowing whether it is already inside a transaction, and be right either way. The query builders and
dynamic_dto come with it.
Migrations where keeping this database's schema up to date is this application's job rather than
somebody else's. It sits beside the client rather than under it, needing only the driver, so it goes with the
client, without it, or not at all. OctaviusMigrator(dataSource).migrate() at startup is the whole of it.
Adding the client later costs nothing: it wraps no query and renames no method, so driver code keeps working unchanged next to it.
inline with a
reified parameter, and a reified function can only be inlined into Kotlin, never called. The non-reified
layer underneath is reachable from Java, but it means hand-building a KType for every column you read:
possible, not usable.driver-spring-integration only. The core driver has no Spring dependency at all.Pre-1.0 and written by one person. Every push runs the suite against a real PostgreSQL 18, alongside a job that generates certificates and exercises the TLS modes end to end, and a third that points the driver at PostgreSQL 17 to prove the handshake refuses it rather than half-working.
The API is not frozen. The driver is the settled part of it; the client and migrations are early. Every module carries the same version and is released together, so the number says when something shipped rather than how mature it is. None of it has seen long production use.
Every change is recorded in the CHANGELOG, grouped by module under each version — one file, because one version covers all of them and a release where only half the repository moved reads oddly split across two.
API Reference — generated KDoc for every
declaration, rebuilt on each push to master. Reach for it when you need a signature, a property, or the
values of an enum.
The guides cover what a signature cannot show — how the pieces behave together. There are three sets, because the driver stands on its own and the client and migrations are each optional beside it: the documentation index points at all three, and the driver's Quickstart is where to start from an empty project.
octavius-database is the previous generation and is
superseded by what is here. Much of it existed to work around pgjdbc — text-protocol composites, enums the
library had to be taught, a stateful ResultSet, no named parameters — and the driver answers all of that
natively. What was left over is the client, which is deliberately a much smaller thing than a port would have
been.
Licensed under the Apache License, Version 2.0.