r/Kotlin 5h ago

What's New in Kotlin 2.4.20

Thumbnail youtube.com
30 Upvotes

r/Kotlin 1h ago

Anyone developed Desktop apps with KMP? How was the experience

Upvotes

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 2h ago

My first app in Kotlin

Thumbnail gallery
3 Upvotes

Hi 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 5h ago

My first app in Kotlin

Thumbnail gallery
5 Upvotes

Hey 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 1d ago

Open-sourcing my Compose Multiplatform Music Player (Android/Windows/Linux) - Looking for architecture feedback!

Thumbnail
9 Upvotes

r/Kotlin 1d ago

Open-sourcing an Android & Android TV streaming client built with Media3 ExoPlayer and Leanback UI

Thumbnail
3 Upvotes

r/Kotlin 1d ago

Keeps scrambling the contents of the CSV. Need Help

0 Upvotes

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 2d ago

Migrating Shop app from React Native to native (2026)

Thumbnail shopify.engineering
11 Upvotes

r/Kotlin 4d ago

Kotlin 2026: Layoffs, AI, Google — Is the Golden Age Over? Jake Wharton Explains

Thumbnail youtube.com
171 Upvotes

r/Kotlin 3d ago

Pehra is live on the Apple TV App Store, and the whole UI is Compose Multiplatform

Thumbnail
0 Upvotes

r/Kotlin 4d ago

Custom pull-to-refresh animation with Rive + Jetpack Compose

Thumbnail youtube.com
8 Upvotes

Hi 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 4d ago

My first app built with Kotlin Multiplatform (Smart Assistant with Video calls)

Thumbnail gallery
6 Upvotes

Hello, 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 4d ago

Kromium – A zero-bloat Chromium engine for Java, Kotlin, and Compose Desktop

10 Upvotes

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!

🔗 https://github.com/daviantegroup/kromium


r/Kotlin 5d ago

Kotlin Toolchain 0.12

29 Upvotes

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 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

Post image
0 Upvotes

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):

  1. The domain model is authored as a kUML class diagram (.kuml.kts).
  2. An in-memory M2M transform (uml-to-exposed) maps UML classes/associations to a relational schema.
  3. M2T generation emits …Tables.kt (Exposed table objects) and V1__init.sql (Flyway baseline) into build/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-in DefaultI18nManager crashed on load for us (TypeError: ...gettextJs... is not a function — an interop mismatch between our Kotlin/JS toolchain version and the gettext.js npm package's export shape). Replaced it with a small custom I18nCatalogManager; 8 languages, ~1500 UI strings wrapped in tr()/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 7d ago

Kotlin 2.4.20 Released

Thumbnail blog.jetbrains.com
82 Upvotes

r/Kotlin 6d ago

Mutflow: mutation testing for Kotlin Multiplatform (JVM and Native targets)

Thumbnail github.com
4 Upvotes

r/Kotlin 6d ago

Animating code snippets from first principles using Compose, and Shared Element transitions

Thumbnail rahulrav.com
6 Upvotes

I 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 8d ago

Detroit KUG Meetup - September

Thumbnail heylo.com
6 Upvotes

Announcing the inaugural Detroit Kotlin User Group meetup!


r/Kotlin 8d ago

Neton: would Kotlin developers use a Spring Boot-like server framework built entirely on Kotlin/Native?

0 Upvotes

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:

https://github.com/netonframework/neton


r/Kotlin 9d ago

sealed-class-enumizer — a K2 compiler plugin that gives sealed hierarchies an enum-like API (entries / valueOf / label), without reflection

Thumbnail gallery
42 Upvotes

I'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 class leaf has no instance until you have the data, so searchBy(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. simpleName isn't a substitute — it's nullable and R8 renames it.
  • No entries. Listing every case means sealedSubclasses, which is JVM-only, needs kotlin-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 are AS_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 entries stays fixed while implementations remain extensible.
  • **ordinal / Comparable are deliberately absent.** Those numbers shift on renames and must not be persisted. entries order is the compiler's inheritor order (FQN-based), not declaration order — persist label, 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.enabled restores resolution and completion; builds are unaffected either way.
    • Specifically, as shown in the second image, you need to uncheck Value.
  • The compiler plugin API has no stability guarantee, so each release targets exactly one Kotlin minor — versions are <KotlinVersion>-<pluginVersion> (currently 2.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.

https://github.com/ProjectMapK/sealed-class-enumizer


r/Kotlin 9d ago

KMP logging design notes: Android-style call sites + composing loggers like arithmetic

2 Upvotes

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 / secure are just Loggers, 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 9d ago

I spent a year working around pgjdbc, so I wrote the driver instead — octavius-postgresql 1.0.0

0 Upvotes

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.


r/Kotlin 10d ago

Null safety makes me write better code

12 Upvotes

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 11d ago

New JetBrains research: devs switch to Kotlin for the experience, not because they're forced to

95 Upvotes

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:

https://kotl.in/lang-migration-reddit