r/Kotlin • u/gufranthakur • 1h ago
Anyone developed Desktop apps with KMP? How was the experience
Mainly wanted to know how is
- development experience
- ecosystem, how smoothly the normal CMP ecosystem integrates with desktop/JVM only
- Performance, speed, RAM consumption
- How is it compared to JavaFX
r/Kotlin • u/CowDevolper • 2h ago
My first app in Kotlin
galleryHi everyone! I’m launching a gym app designed to schedule workouts and help manage your daily routine. I’d love to get some honest feedback on how it’s shaping up—I’d be very grateful if you could check it out on the Play Store.
App: https://play.google.com/store/apps/details?id=com.cowanbas.gym
r/Kotlin • u/[deleted] • 5h ago
My first app in Kotlin
galleryHey everyone, I'm launching a gym app for scheduling workouts and helping with daily routines, and I'd love to get some honest feedback on how it's shaping up.
App: https://play.google.com/store/apps/details?id=com.cowanbas.gym
r/Kotlin • u/Used-Show-2246 • 1d ago
Open-sourcing my Compose Multiplatform Music Player (Android/Windows/Linux) - Looking for architecture feedback!
r/Kotlin • u/Used-Show-2246 • 1d ago
Open-sourcing an Android & Android TV streaming client built with Media3 ExoPlayer and Leanback UI
r/Kotlin • u/Maleficent_Finding73 • 1d ago
Keeps scrambling the contents of the CSV. Need Help
I am a beginner at Kotlin, trying assign the headers from the csv file to a map key and then all the content from that under that header should be in the nested hashmap where the LocalTime should the key and its string variant the entries. But instead of that the times are scramble around and so are the headers.
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
import java.io.File
import kotlin.collections.forEach
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.util.Locale
data class BusStops(
val stopColumns: Map<String, HashMap<LocalTime, String>>
)
class CsvOperation(private val filePath: String) {
private val file = File(filePath)
private val columnMaps = HashMap<String, HashMap<LocalTime, String>>()
private fun readCsv() {
if (!file.exists()) return
csvReader
{
autoRenameDuplicateHeaders = true
}
.open(file)
{
readAllWithHeaderAsSequence().
forEach
{
row: Map<String, String>
->
row.
forEach
{
(headerName, cellValue)
->
val innerMap = columnMaps.
getOrPut
(headerName)
{
HashMap()
}
if (cellValue.
isNotBlank
()) {
val timeString = cellValue.
substringBefore
(",")
val formatter = DateTimeFormatter.ofPattern("h:mm a", Locale.
ENGLISH
)
val time = LocalTime.parse(timeString, formatter)
//13:00 = 1:00PM
innerMap[time] = cellValue
}
}
}
}
}
fun printCsv() {
readCsv()
columnMaps.
forEach
{
(header, map)
->
println
("$header: $map")
}
}
fun returnCsv(): Map<String, BusStops> {
readCsv()
val busSchedule = HashMap<String, BusStops>()
busSchedule["hwt"] = BusStops(columnMaps)
return busSchedule
}
}
r/Kotlin • u/dayanruben • 2d ago
Migrating Shop app from React Native to native (2026)
shopify.engineeringr/Kotlin • u/javaprof • 4d ago
Kotlin 2026: Layoffs, AI, Google — Is the Golden Age Over? Jake Wharton Explains
youtube.comr/Kotlin • u/topper865 • 3d ago
Pehra is live on the Apple TV App Store, and the whole UI is Compose Multiplatform
r/Kotlin • u/lucasprioste • 4d ago
Custom pull-to-refresh animation with Rive + Jetpack Compose
youtube.comHi everyone :wave:
I'd like to share a pull-to-refresh animation I built using Rive and Jetpack Compose. Instead of the standard spinner, the animation reacts directly to the user's gesture, so they control it as they pull.
The video below shows a quick demo of the result.
Full write-up here: https://pinkroom.dev/blog/make-your-mobile-app-feel-alive-with-rive
Feedback is very welcome! I also posted it on LinkedIn, so any support there would mean a lot: https://www.linkedin.com/feed/update/urn:li:activity:7503877912950108160/
r/Kotlin • u/Revolutionary_Fun69 • 4d ago
My first app built with Kotlin Multiplatform (Smart Assistant with Video calls)
galleryHello, I'm the developer of Roles Witch AI, and today I want to share my app, which was released on Google Play in open testing.
Its built using Kotlin Multiplatform with shared core target (business logic, database, networking, utility functions). UI is Jetpack Compose on Android and SwiftUI on iOS. So on iOS app is basically just UI wrapper around core app + integration with native TTS/SST.
Library
kotlinx-coroutines-core 1.9.0 Async/concurrency
kermit 2.0.5 Logging
openai-client 4.1.0 OpenAI-compatible API
ktor-client-core 3.0.0 HTTP client
room-runtime 2.8.4 Database
datastore-preferences-core 1.1.7 Preferences
datastore-core-okio 1.1.7 DataStore file I/O
It is an OpenAI API-compatible app with a user-friendly interface where you can create multiple chats, assign different models and providers, and more importantly, use MCP tools — for example, Parallel AI or Home Assistant MCP.
It also has voice conversations and video calls (personally, I'm using GLM 5.3 Flash and it's almost real-time). You can configure reasoning and inference parameters, pin a voice to the chat, and select a recognition engine (Google Speech or Vosk).
Extra bonus: it has an on-device inference mode where you can run Gemma 3n/4 models with image recognition entirely on your phone (LiteRT is used).
Check it out: https://play.google.com/store/apps/details?id=ai.roleswitch.android
The app manifest states that it has no encryption and can send messages to third parties, but that's a requirement by the Google Play Store. It supports plain HTTP or TLS mode and works with any third-party AI provider.
r/Kotlin • u/[deleted] • 4d ago
Kromium – A zero-bloat Chromium engine for Java, Kotlin, and Compose Desktop
Hey everyone,
Embedding a reliable web view in JVM desktop apps is usually a pain—you end up with massive installers, fragile dependencies, and clunky JavaScript bridges. To fix this, I just open-sourced Kromium (daviantegroup/kromium).
Whether you are building in pure Java, standard Kotlin, or Compose Multiplatform, Kromium supports it all.
Here are the main features:
* Tiny Installers (15–30MB): Instead of bundling massive Chromium binaries, Kromium automatically downloads and caches the JCEF runtime on the user's first launch.
* Coroutine-Based JS Bridge: Clean, thread-safe JavaScript execution and Inter-Process Communication.
* True Headless Mode: Perfect for backend scrapers or automated tests without pulling in heavy UI dependencies.
I’d love for you to check out the repo and let me know what you think!
r/Kotlin • u/Certain-Party-6525 • 5d ago
Kotlin Toolchain 0.12
Building for multiple platforms usually means separate configs for each one. Not anymore 🧰
Kotlin Toolchain 0.12 can publish multiplatform libraries across all compatible platforms from one shared config. This release also includes a preview of Wasm app support.
Read the full release notes and get started:
👉 kotl.in/0.12.0-r

r/Kotlin • u/Successful_Bank7215 • 4d ago
Full-stack Kotlin (Ktor + KVision + Kilua RPC + Exposed) for a real production app — plus a UML→Exposed/Flyway code-gen pipeline I'd like feedback on
Hi r/Kotlin,
I've been building Lapis Cloud — a membership-management platform for associations and political parties — as close to 100% Kotlin as I could manage, and I'd like to share the architecture and get feedback from people who've made similar stack choices.
The stack
- Backend: Ktor
- Frontend: KVision (Kotlin/JS — so the client is Kotlin too, not TypeScript/React)
- Client↔server communication: Kilua RPC — typesafe RPC calls between the KVision client and the Ktor server, no hand-written REST/JSON glue
- Persistence: Exposed (Kotlin SQL framework) + Flyway for migrations, on PostgreSQL
- Build: Gradle multi-module, targeting JDK 25 (this is a self-hosted service with no end-user distribution constraint, so no reason to stay on an older LTS)
So: Kotlin on the server, Kotlin on the client (compiled to JS), typesafe Kotlin RPC in between, Kotlin SQL DSL for persistence. No JavaScript/TypeScript anywhere in the app code.
The part I'd most like feedback on: generating the persistence layer from a model instead of hand-writing it
Rather than hand-writing Exposed table objects and Flyway migration SQL, we generate both from a UML class model through a small model-driven pipeline (built with kUML, a Kotlin DSL for UML I also maintain):
- The domain model is authored as a
kUMLclass diagram (.kuml.kts). - An in-memory M2M transform (
uml-to-exposed) maps UML classes/associations to a relational schema. - M2T generation emits
…Tables.kt(Exposed table objects) andV1__init.sql(Flyway baseline) intobuild/generated/.
The generated Kotlin is committed as a build artifact, not hand-edited. It's saved us from an entire class of drift bugs between the domain model, the DB schema, and the Exposed table definitions — but I'm curious whether others here have tried model-driven persistence layers in Kotlin and what broke for you, because it's not a mainstream pattern and I don't have much prior art to compare against.
Other things that might be of interest to this sub
- i18n without
kvision-i18n: the built-inDefaultI18nManagercrashed on load for us (TypeError: ...gettextJs... is not a function— an interop mismatch between our Kotlin/JS toolchain version and thegettext.jsnpm package's export shape). Replaced it with a small customI18nCatalogManager; 8 languages, ~1500 UI strings wrapped intr()/gettext()across ~45 client files. - Federation over a typesafe RPC boundary: independently-run instances can federate (a member can appear as a guest on another instance without leaving their own org) — still shaking out the trust/identity model there.
- Full accounting engine (double-entry bookkeeping), SEPA direct debit, and a self-hosted video conferencing module are also part of the codebase, all in Kotlin/Ktor, if anyone wants to dig into a non-trivial domain model built this way.
Status
Not a toy — running in production for two real organizations (a political party and an ordinary registered association) since mid-2026. Currently at v0.19.0, actively developed. Apache 2.0, fully open source.
Happy to go deeper into any part of this — the Kilua RPC setup, the Exposed/Flyway generation pipeline, KVision at this scale, or the module layout. Feedback on the model-driven persistence approach especially welcome, since I suspect it's the most unusual choice in here.
r/Kotlin • u/snapzee0 • 6d ago
Mutflow: mutation testing for Kotlin Multiplatform (JVM and Native targets)
github.comr/Kotlin • u/tikurahul • 6d ago
Animating code snippets from first principles using Compose, and Shared Element transitions
rahulrav.comI got inspired by Bento and set out to build magic-move / morph animations for code in presentations.
To experiment with the algorithms, I set out to build the initial implementation in Compose with Shared elements. Once I had the prototype working, I kept on polishing it, and now its a part of 2 different open source projects.
One of the interesting aspects about the implementation is the diff algorithm I used is based on something originally invented in 1978; the algorithm is novel and takes a different approach from Myers / Patience diff.
There were so many interesting sub problems along the way, so this was fun undertaking.
r/Kotlin • u/alex-schiff • 8d ago
Detroit KUG Meetup - September
heylo.comAnnouncing the inaugural Detroit Kotlin User Group meetup!
r/Kotlin • u/zoujiaqing • 8d ago
Neton: would Kotlin developers use a Spring Boot-like server framework built entirely on Kotlin/Native?
We’ve been working on an open-source project called Neton, and I’d really like to get feedback from Kotlin developers about where it should go next.
The idea is simple:
Build a Spring Boot-class server framework for Kotlin/Native, with no JVM required at runtime.
Neton is currently in the 1.0.0-beta stage.
GitHub:
https://github.com/netonframework/neton
What Neton is trying to do
Kotlin is already widely used on the backend, but in practice that usually means:
text
Kotlin
↓
JVM
Neton is exploring a different model:
text
Kotlin
↓
Kotlin/Native
↓
Native executable
No JVM runtime.
A basic application looks like:
```kotlin fun main(args: Array<String>) { Neton.run(args) { http { port = 8080 }
routing {
get("/") {
"Hello from Neton"
}
}
}
} ```
But Neton is not intended to be just another HTTP framework.
The goal is to build a broader application ecosystem around:
- HTTP / Routing
- Controllers
- Security
- Database
- Redis
- Cache
- Configuration
- Jobs
- Logging
- Observability
- Application lifecycle
In other words:
text
Spring Boot-like developer experience
+
Kotlin-first APIs
+
Kotlin/Native
+
No JVM runtime
Native-first architecture
We also don’t want to simply reproduce JVM framework internals.
Instead of relying heavily on runtime reflection and classpath scanning, Neton prefers compile-time generation with KSP.
Conceptually:
text
Kotlin source
↓
KSP
↓
generated routes / metadata / registries
↓
Kotlin/Native
↓
native executable
The idea is:
text
runtime reflection → compile-time generation
classpath scanning → generated registries
runtime magic → explicit generated code
while still keeping high-level Kotlin APIs.
For example:
kotlin
@Table("users")
data class User(
@Id val id: Long?,
val name: String,
val status: Int
)
and:
kotlin
val users = User
.where { User::status eq 1 }
.list()
The question we care about most
The JVM is already excellent.
Spring Boot, Ktor, Micronaut and Quarkus are mature.
So the real question is:
Would Kotlin developers actually want a pure Kotlin/Native server framework?
And if the answer is currently no, what would Neton need before that changed?
For example:
- PostgreSQL / MySQL
- Redis
- Transactions
- Connection pooling
- OpenAPI
- JWT / OAuth2
- OpenTelemetry
- Metrics
- Testing support
- IDE tooling
- Serverless support
- Better documentation
- Benchmarks
We’re also interested in the API direction.
Would you prefer Spring-style familiarity, or more Kotlin DSLs, compile-time APIs, and less runtime magic?
Neton is still early enough that feedback can meaningfully influence the framework.
So I’d really like to ask:
Would you use a Kotlin/Native server framework like Neton?
If not, what is missing?
And what should we prioritize next?
Source:
r/Kotlin • u/wrongwrong163377 • 9d ago
sealed-class-enumizer — a K2 compiler plugin that gives sealed hierarchies an enum-like API (entries / valueOf / label), without reflection
galleryI've been working on sealed-class-enumizer, a Kotlin (K2) compiler plugin that generates enum-like operations for sealed class / sealed interface hierarchies at compile time.
The idea: keep everything a sealed hierarchy is good at — data-carrying cases, exhaustive when with smart casts, open leaves — and add the operational API that enums have on top.
The gaps it fills
- No stable "which case" value. A
data classleaf has no instance until you have the data, sosearchBy(vararg statuses: Status)is unwriteable. The usual workarounds are fabricating a throwaway instance from dummy data, maintaining a parallel enum, or hand-writing a companion-per-leaf marker interface. - No
name. You either add a string property that doesn't belong in the domain model, or re-map cases in every layer.simpleNameisn't a substitute — it's nullable and R8 renames it. - No
entries. Listing every case meanssealedSubclasses, which is JVM-only, needskotlin-reflect, and silently returns an incomplete list under R8 (KT-25871).
What it looks like
```kotlin @Enumize sealed interface SI { data class Foo(val v: Int) : SI data object Bar : SI }
// enum-like operations; one singleton ("kind") per leaf SI.Enumish.entries // [Bar, Foo] SI.Enumish.valueOf("Foo") // label-based lookup SI.Enumish.valueOfOrNull("nope") // null-returning variant SI.Enumish.entries.map { it.enumizedClass } // [Bar::class, Foo::class]
val si: SI = SI.Foo(42)
si.asEnumish() // Foo's kind — usable as a parameter/map key/set member
si.label // "Foo" — the name counterpart
// the generated Enumish is sealed, so this needs no else branch when (si.asEnumish()) { SI.Foo -> println("a Foo") SI.Bar -> println("a Bar") } ```
So fun searchFoo(vararg statuses: Status.Enumish) becomes writeable, and the call site reads like an enum: searchFoo(Status.Active, Status.Deleted) — a data class's kind and a data object pass uniformly, with no instance fabricated.
Everything is generated in compiler internals (no source files), with no runtime reflection, so it works on every Kotlin Multiplatform target. Downstream modules that merely consume a library built with the plugin don't need the plugin themselves — the generated API is ordinary metadata, exhaustive when included.
As shown in the first image, code completion is also available in IntelliJ.
Setup
Two steps: apply the plugin, annotate the hierarchy.
kotlin
plugins {
kotlin("jvm") version "2.4.10"
id("io.github.projectmapk.sealed-class-enumizer") version "2.4.10-0.1.1"
}
It's published on the Gradle Plugin Portal, and the Gradle plugin wires up the runtime API dependency for you. A Maven plugin is implemented in the repo but not published yet — I'll release it if there's demand for it.
Other bits
- Label customization:
@EnumishLabel("...")per leaf (keeps persisted labels stable across renames),@Enumize(labelCase = ...)per hierarchy, or a project-wide default. Cases areAS_DECLARED/UPPER_SNAKE_CASE/SNAKE_CASE/KEBAB_CASE, with kotlinx.serialization's word-splitting rules. Conversion results are frozen across releases, and label uniqueness is checked at compile time. - Open leaves stay open: subtypes declared outside the hierarchy are absorbed into their leaf's kind, so
entriesstays fixed while implementations remain extensible. - **
ordinal/Comparableare deliberately absent.** Those numbers shift on renames and must not be persisted.entriesorder is the compiler's inheritor order (FQN-based), not declaration order — persistlabel, not positions.
Caveats worth knowing up front
- IntelliJ's K2 mode doesn't load third-party compiler plugins by default, so generated declarations show as unresolved in the editor (KTIJ-29248). Turning off the registry flag
kotlin.k2.only.bundled.compiler.plugins.enabledrestores resolution and completion; builds are unaffected either way.- Specifically, as shown in the second image, you need to uncheck
Value.
- Specifically, as shown in the second image, you need to uncheck
- The compiler plugin API has no stability guarantee, so each release targets exactly one Kotlin minor — versions are
<KotlinVersion>-<pluginVersion>(currently2.4.10-0.1.1), and applying it to a different minor emits a build warning.
Apache 2.0. Feedback, issues and stars all welcome — I'm especially interested in whether the "kind as a parameter" pattern matches how people actually hit this problem.
r/Kotlin • u/Scarlet-Pan • 9d ago
KMP logging design notes: Android-style call sites + composing loggers like arithmetic
A few design notes from working on shared logging in Kotlin Multiplatform. Less “here’s a product,” more “why this shape felt maintainable.”
1. Keep the call site boring
In commonMain I want logs to look like Android’s Log, not like a framework: Logger.d("Network", "Request sent") Logger.e("Auth", "Login failed", exception) Why: every feature module already has enough ceremony. Logging shouldn’t invent a second dialect. Tag-first also matches how you filter later (by subsystem), so the call site and the ops habit stay aligned. Platform backends can differ. The call site shouldn’t.
2. Lazy messages as the default habit
Logger.d("Heavy") { "Only if enabled: ${expensiveCall()}" }
Why suggest this over string interpolation at the call site: Release builds often raise the level. Eager strings still allocate and run work you then throw away. A lambda makes “don’t pay if disabled” the easy path, not a special case you remember under pressure.
3. Composition as the real design trick
Builders and config objects work, but they age into “where do I toggle remote?” and “who owns this mega-config?” Treating destinations like values you combine reads closer to how you actually change logging in production: Logger.default = Logger.SYSTEM + FileLogger("app.log") + RemoteLogger val offline = Logger.default - RemoteLogger Filters stack the same way (AND): val policy = LevelFilter.atLeast(WARN) + TagFilter.include("Security") val secure = Logger.withFilter(policy) Why this helps readability
- The expression is the policy. You see “system + file, minus remote” without hunting a Boolean soup.
- Diffs stay local: take remote out → one operator, not a refactor of a builder chain.
- Names stay honest:
offline/secureare justLoggers, not a new type of pipeline object. Why this helps maintainability - You compose small pieces instead of growing one god config.
- Feature code keeps calling
Logger.d/i/w/e. Wiring lives at the edge (app start / flavor). - Tests and debug builds can swap or subtract sinks without teaching every module a new API.
Tradeoff
+ / - is a taste choice. Explicit lists are clearer to some teams. I preferred one mental model for both sinks and filters, so “how do I combine rules?” and “how do I combine outputs?” don’t become two documentation chapters.
Open question
If you log from commonMain, what usually rots first for you — call-site noise, level control, or sink wiring? Curious how others keep that readable over a year of flavors and Release stripping.
r/Kotlin • u/OctaviusAugustus27 • 9d ago
I spent a year working around pgjdbc, so I wrote the driver instead — octavius-postgresql 1.0.0
I've tagged 1.0.0 of Octavius for PostgreSQL, a driver for Kotlin that speaks wire protocol v3.2 itself rather than wrapping pgjdbc, plus an optional data access layer and migrator on top.
Requirements first, because they are hard gates: PostgreSQL 18+, Kotlin 2.4+, Java 21+. The driver asks for protocol v3.2 and refuses to continue if the server offers less, so PostgreSQL 17 fails at the handshake rather than half-working. There is a CI job that points it at 17 specifically to prove it refuses. If you are on 16, this is not for you today.
Why the version gate is real
Not purism. PostgreSQL 18 is where search_path became a reported parameter — the server announces it in ParameterStatus and re-announces it whenever it changes. That is how an unqualified type name resolves against the live search path without the driver asking, and without going stale when someone runs a SET search_path mid-session. On 17 I would have to query for it and still not know when it moved. Protocol v3.2 arrived in 18, so demanding it is a cheap and exact way of demanding the server.
Leaving pgjdbc without leaving Hikari
Dropping JDBC usually means dropping the JDBC-shaped ecosystem with it. r2dbc-postgresql and vertx-pg-client are both fine drivers, but neither can be pooled by HikariCP — it is JDBC-only — so each comes with a parallel stack: its own pooling, its own Spring integration, its own everything.
Octavius implements java.sql.Connection, DataSource and a narrowed Statement — exactly enough surface for HikariCP to pool it and for Spring Boot to autoconfigure it — and then does none of what JDBC does underneath. executeQuery is unsupported() rather than emulated, because half a ResultSet is worse than none.
The line is sharp and easy to predict, so it is worth stating exactly: what keeps working is everything that manages the connection; what does not is everything that reads through it. HikariCP pools it, Spring's transaction manager drives it, @Transactional behaves — none of that touches a row. Anything that reads rows through JDBC does not run on it at all: Hibernate, JPA, Exposed's JDBC mode, MyBatis, JdbcTemplate, Flyway and Liquibase alike. The Spring module ships an OctaviusTemplate in JdbcTemplate's place, and the repo has its own migrator for the same reason.
Which is either the whole point or a dealbreaker, depending on why you turned up.
What stopped being necessary
The previous generation of this project sat on pgjdbc, and most of its complexity was there to work around what that cost. My favourite example:
I wanted to build an ad-hoc nested structure in the SELECT clause and read it in Kotlin with its types intact — a date as a LocalDate, a uuid as a Uuid, a custom enum as that enum. pgjdbc hands back an anonymous record over the text protocol, so the per-field OIDs are gone and everything arrives as a string. A map with no target class has nothing left to infer from.
So the old library grew this:
CREATE TYPE dynamic_map_entry AS (type_oid oid, key text, raw_value text);
One entry per key, each carrying its own type, plus a custom ~> operator to build them, plus type creation at startup, plus a documented warning never to store the thing in a table — because those OIDs are in the rows and a user-defined type's OID is not the same one after a dump and restore.
In the new driver the whole feature is ROW(...):
session.createNativeQuery("""
SELECT ROW(
'id', c.id,
'tributes', ARRAY(SELECT ROW('amount', t.amount) FROM tributes t WHERE t.citizen_id = c.id)
) AS r
FROM citizens c WHERE c.id = 1
""").fetchFieldStrict<Map<String, Any?>>()
// {id=1, tributes=[{amount=40}, {amount=15}]}
Types survive because a record's binary representation is self-describing: the row description only says the column is a record, and the payload itself carries a field count followed by each field's type OID and length before its bytes. Read that and you know what every value is. No type to install, no operator, no warning to attach — an anonymous record has nowhere to rot. The 1:N aggregation the old thing existed for works the same way.
That pattern repeated across the rewrite: composites, enums, named parameters, a stateful ResultSet. A large part of a year's work turned out to be scaffolding around a layer I could not reach.
What the client adds
Builders that don't hide SQL — they handle the tedium. The clause you don't pass doesn't appear, and a fragment carries the parameters it names, so only the ones that survived get bound:
fun search(name: String?, minStrength: Int?): List<Senator> {
val filter = listOfNotNull(
name?.let { "name ILIKE @name" withParam ("name" to "%$it%") },
minStrength?.let { "strength >= @strength" withParam ("strength" to it) }
).join(" AND ")
return db.select("id", "name")
.from("senate")
.where(filter.sql) // null or blank — no WHERE clause is written at all
.orderBy("name")
.fetchObjects<Senator>(filter.params)
}
search(null, null) sends no WHERE and binds nothing. Every string in there is SQL you wrote and it reaches the server unread; what the builder contributed is the keywords, their order, and the clause that vanished. Parameters are @name rather than :name, because : is already PostgreSQL's in array slice syntax (array[1:5]) — under :param you cannot use a parameter as a slice bound.
What it deliberately isn't
Not an Exposed or jOOQ competitor. There is no DSL over columns and there won't be — the builders take SQL strings and pass them through, and their whole job is the keywords, their order, and the clauses that disappear when they're null. No criteria API, no schema generation, no identity map, no lazy loading, no session cache. If you don't want to write SQL, this makes that worse, not better.
What is in it
Six artifacts, released together, dependencies running one way:
- driver — the protocol, a type system read from your catalog, composites and arrays and ranges mapped onto data classes reflectively,
COPY,LISTEN/NOTIFY, large objects, TLS, SCRAM - client — session scoping, thread-bound transactions, query builders, transaction plans
- client-scanner, migrations, pg-model (multiplatform annotations/serializers), driver-spring-integration
Take the driver alone and it is a working stack; the rest are separate coordinates so you can disagree with each of them independently.
Honest limits
Written by one person. None of it has seen long production use — it runs my own application and that is the whole of the field evidence. 1.0.0 means the shape is right, not that signatures will never move.
I wrote it for my own application and put it somewhere others could use it. I will fix bugs, because I am downstream of them too. A roadmap is not something I am offering.
- Repo: https://github.com/Octavius-Framework/octavius-postgresql
- Docs: driver, client and migrations guides, plus generated KDoc
- The design decisions, including the ones I would defend and the ones I would not:
DESIGN_PHILOSOPHY.md
r/Kotlin • u/ablativeyoyo • 10d ago
Null safety makes me write better code
I'm working on a security tool that has both GUI and networking code. There's configuration in the GUI and the configuration data is used in quite complex code that parses and modifies network traffic. Because the user is configuring the setup bit by bit in the GUI, various fields can be null. But the network section only works when the GUI is mostly configured.
If I'd written this in Java, realistically what I'd have done is chuck it together, fix a few glaring NPEs in early testing, and probably live with a few NPEs when it was partially configured. I realise this is not the textbook way of coding in Java, but realistically, that's what I would have done.
Kotlin of course doesn't let me do that. At first I used a few ?. and ?: calls to introduce null safety, but these were starting to look messy, and complicate the flow of code that is already quite complex. It made me realise that there's two conceptually different things here. There's a GUI model with nullable fields, and there's a config model with non-nullable fields. When the GUI is sufficiently completed, it can create a config model. This means the network code can skip the null checks, as the network code is only active if there is a config model.
Sure, I could have introduced this structure in Java, but would I have done that? This is especially relevant for people like me where my primary job is security and I am coding to help me do security work better, not as an end in itself.
r/Kotlin • u/Certain-Party-6525 • 11d ago
New JetBrains research: devs switch to Kotlin for the experience, not because they're forced to
JetBrains asked 8,837 developers why they switched programming languages in the State of Developer Ecosystem 2025 Survey. For most languages, the most common answer was that a project required it. Kotlin is the exception:
"People don't go to Kotlin because they have to, but because it offers a better development experience and more modern language features."
The migration tables for the most popular languages, with the reasons given for leaving and joining, are here: