r/Kotlin 7h ago

Gradle is very confusing

Post image
Upvotes

Imagine a project that consists of three apps (mobile, server, desktop).

Most people would see it this way:

sh horse-tinder # Project │ ├── build.gradle ├── settings.gradle ├── gradle.properties │ ├── server # Module │ └── build.gradle │ ├── mobile # Module │ └── build.gradle │ └── desktop # Module └── build.gradle

But in Gradle whole product is called a build not a project. Where sub-parts of a build are called projects not modules. So more accurate description looks like this:

sh horse-tinder # Project (as a gradle project) | Build (Gradle powered project) │ ├── build.gradle # Optional configuration file for root project. ├── settings.gradle # Defines a build. Isn't strictly required (a single-project build runs without it) ├── gradle.properties # Variables that configure Gradle binary itself. │ ├── server # Project │ └── build.gradle │ ├── mobile # Project │ └── build.gradle │ └── desktop # Project └── build.gradle

What is a Build?

  • Is defined by settings.gradle file (not build.gradle).
  • Includes set of projects or a single project.
  • A build can refere to multiple things:
    • The build meaning whole gradle project.
    • Task called build.
    • Product/output of a build task.
    • Process of building a project.
    • Process of building the build.

What is a Project?

  • They get defined using tree structure in settings.gradle file:

groovy include(":libs:a") include(":libs:b") include(":server") include(":client")

  • Each node (libs, a, b, server, client) in this tree defines a separate project.
  • By default Gradle looks for projects using same directory tree. But path to each project can be manually defined.

sh horse-tinder │ ├── server │ └── build.gradle │ ├── client │ └── build.gradle │ └── libs └── a └── build.gradle └── b └── build.gradle

  • Intermediate nodes in a tree (eg. libs) do not require their own build.gradle.
  • Root project exists by default. Root build.gradle is optional configuration file. Its not something to be included in settings.gradle.
  • Project can be addressed either by a absolute path (eg. :composeApp) or a relative path (eg. composeApp).

What is a Task?

  • A unit of work.
  • Always belongs to specific project.

What are Gradle properties?

  • Variables in format of key-value pairs defined in gradle.properties.
  • Private defaults are stored in ~/.gradle/gradle.properties

What is versions file?

  • Central list of versions and libraries defined in libs.versions.toml file.
  • Stored in gradle folder.

What is a Gradle wrapper?

  • A small launcher that bundled with a repository that downloads the pinned Gradle version (from gradle-wrapper.properties) on first use and reuses the cached copy after.

Why it is so confusing?

People: These are modules of a project. These modules can consist of sub-modules. Gradle: No! These are projects of a single build. Projects are organized into a tree structure but it means nothing other than organization. People: This build.gradle file probably has something to do with a build. Gradle: Wrong guess again! These files describe projects. People: If projects are organized into a tree structure then root build.gradle must be very important. Gradle: Naaah. Its optional. People: So whole project is called a build and modules are in fact called projects which are defined by files called build.gradle?! Gradle: Exactly!

So to avoid confusion build.gradle should be named module.gradle and settings.gradle should be named build.gradle.


r/Kotlin 20h ago

Kotlin 2.4.20 Released

Thumbnail blog.jetbrains.com
Upvotes

r/Kotlin 10h ago

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

Thumbnail rahulrav.com
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 5h ago

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

Thumbnail github.com
Upvotes

r/Kotlin 1d ago

Detroit KUG Meetup - September

Thumbnail heylo.com
Upvotes

Announcing the inaugural Detroit Kotlin User Group meetup!


r/Kotlin 1d ago

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

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

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

Thumbnail gallery
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 3d ago

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

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

I spent a year working around pgjdbc, so I wrote the driver instead — octavius-postgresql 1.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 4d ago

Null safety makes me write better code

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

KMP Mindset

Thumbnail gallery
Upvotes

Kotlin Multiplatform is an architecture-first approach to multiplatform development, i.e. to running your business logic across various platforms.

Let's establish some definitions first.

Machine : A machine is a combination of a CPU and an operating system on which software can run.

a. Native machine: Software targets a specific CPU–OS combination directly. As the number of CPU–OS combinations grew, software had to account for an increasing number of machine types.

b. Virtual machine: Virtual machines were introduced to abstract away these CPU–OS differences. Software could instead target a virtual machine through a runtime such as the JVM, Python runtime, or Dalvik. The runtime then handled the differences between the underlying machines.

In both cases, the target was still fundamentally called a “machine”.

Platform :Web browsers introduced a new type of software target. But a browser was neither a native machine nor a virtual machine. Thus, specifying a machine was no longer sufficient to describe a software target. We started using the term “Platform” to define our targets.

Artifacts : An artifact is any output that allows end-users or other software to consume your product. Artifacts vary based on machine/platform differences (CPU, OS, runtime, browser engine, etc.) and by whether they are libraries or executables.

Library : An artifact intended to be consumed by another program rather than directly executed by an end-user. It provides reusable functionality through a defined interface. A library may consist of a single underlying library artifact or a collection of libraries and supporting artifacts packaged together as one consumable unit. An SDK is a library-oriented distribution that may additionally contain tools, documentation, examples, configuration, and other resources for developers.

Executable : An artifact intended to be launched by an execution environment and ultimately used to perform a complete program. An executable may consist of multiple underlying artifacts packaged together with an entry point and required resources. A user-facing executable is commonly called an app.

So, even before writing the first line of code, you must have clarity about which parts of your product are meant to be libraries and which are meant to become executables. A library provides capabilities to another program, while an executable provides a complete program for an execution environment. This distinction gives us an architectural boundary between the software we build and the platform-specific details required to package and run it.

Kotlin Multiplatform takes advantage of this boundary: rather than making the entire application itself multiplatform, it primarily allows us to make a library of shared logic multiplatform and then consume that library from platform-specific executables.

Now we are forced to bifurcate or decompose our software into libraries. Depending on our product and system, we might choose to create one library or multiple libraries, since a library can be considered a unit of software that can be consumed independently. We might also want to draw a clear API contract around a library so that its functionality can be consumed by other libraries without exposing its internal implementation.

Now we need a way to represent these libraries within our project and define how they relate to one another. This is where modules come in. With Gradle, we can decompose our project into modules and use Gradle primarily as a dependency management and build system to connect them. Each module can depend on other modules according to the architecture we want to enforce, allowing us to define the direction of dependencies and therefore the intended flow of the system.

So, even though we haven't written a single line of code, we have already formed a rough mental model of our system: what its libraries might be, what their API boundaries could look like, and how they might depend on one another.

And importantly, we did all of this without thinking about any platform stuff. We are reasoning purely about the business logic and the architecture of the system—things that, at this level, have nothing inherently to do with Android, iOS, Windows, Linux, or any other platform.


r/Kotlin 3d ago

Music player

Upvotes

Streaming & Playback: Plays any YouTube Music song or local file in the background (even with the screen off).Smart Search: Finds songs, albums, and playlists, and opens YouTube links directly in the app.Offline Mode: Caches (saves) streamed songs automatically so you can listen without internet.Playlists: Lets you create custom playlists and import existing playlists directly from YouTube.Premium Extras: Features synchronized scrolling lyrics (with a manual editor), a sleep timer, volume normalization, and Android Auto support.Custom UI: Uses a modern "Material You" design that matches your phone's wallpaper colors.

How i make I want to open source and how to do it steps by step and also tell me to use newpipe Extractor


r/Kotlin 4d ago

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

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


r/Kotlin 4d ago

Kotlin Toolchain 0.12: Multiplatform Library Publishing, Wasm Apps

Thumbnail blog.jetbrains.com
Upvotes

r/Kotlin 4d ago

I built a product tour library for Compose Multiplatform

Post image
Upvotes

r/Kotlin 5d ago

K/JS usability issues explored

Upvotes

Someone posted a bunch of usability issues with K/JS, and I wanted to explore them in a new post, mostly to have more space to write and also for better awareness.

  • Big bundle size. Maybe related to coroutines compiling down to continuations. Even worse with kotlinx-serialization which uses code generation.

    This can indeed be a problem, especially when targeting the browser as there is currently no native way to split into chunks. While there is currently no solution, it appears data-flow-driven optimizations are being explored, see KT-87404. Strictly speaking about coroutines, the state machine is now compiled down to JS generators, which saves quite a bit of space! See KT-81730.

    Additionally, deferred imports when targeting modern ECMAScript (KT-20679) by transpiling with SWC (yes, K/JS does offer an experimental way to transpile with SWC) will definitely help in this regard by improving loading times.

  • No Lazy JS exports: this means that if your class is not available on the window at launch, it's going to break despite the class not actually being used.

    I'm not sure I understand this one to be honest. Maybe I've never encountered it.

  • Enum/Data class mapping onto JS objects and TS enums: right now requires you to write a ton of conversion code to make APIs usable from JS.

    Fair enough! I agree this is actually pretty bad right now. I feel like optimizations could go towards:

    1. Sealed hierarchies should have a discriminator KT-71798
    2. Sealed interfaces with static values should be exportable as unions. This is possible with https://github.com/turansky/seskar#unions.
    3. Constructor arguments should be exported as object parameter KT-63669
  • Lack of a couple of type defs for browser APIs (can't remember anymore which ones exactly, they don't cover everything 100%).

    As far as I know kotlinx-browser is kept fairly up-to-date. The alternative is to use kotlin-wrappers.

  • Webpack and Karma toolchain (is going to get changed).

    Indeed, the idea seems to be allowing proper extensibility, that is, you'll be able use whatever tool you prefer by extending KGP or via DSL. In the meantime, Karma is being replaced with Playwright via a new DSL as you can see at A new DSL for browser testing.

  • Suspend functions to async functions and TypeScript defs were just stabilized in 2.4 I think (buggy support prior if at all).

    True. From 2.4.20 you'll be able to export pretty much any suspend declaration, including suspend lambdas.

  • Bad debuggability. I have to ship debug builds or the stack traces are impossible to read, despite having source maps. Even worse when coroutines are involved.

    Honestly I have not encountered this issue, but maybe it is because I ship to Node.js. That said I believe that and I don't have any real suggestion. In the future, using stuff like Source Map Scopes might give us access to more data while debugging and improve stepping. It appears some work to support that is already being done.

  • No support for mixins (yes people use them).

    This can be done with kotlin-wrappers and seskar. See this test case with the @JsMixin annotation.

  • Lack of documentation. Especially bad for Kotlin Wrappers and Gradle build docs.

    I can comment on the Gradle side of things: it does take time to get used to it. Exploring Kotlin's source code for the compiler and Gradle plugin gives you a lot more insight in what can and cannot be done.

  • TypeScript interface equivalents use a compiler plugin (@JsPlainObject) which still produces red squigglies in IntelliJ and sometimes does not properly auto complete.

    This was an IntelliJ issue. If you can still reproduce in 2026.1 or 2026.2 it is definitely worth reporting it.

  • I don't think there is a great way yet to consume TypeScript type defs (basically translating d.ts files into "expect" typedefs). There's a library but I think it's still experimental.

    Dukat is dead at the moment. What you are probably referring to is Karakum. Karakum supports writing plugins in Kotlin btw! But documentation is still pretty limited so the best bet is to go look at how kotlin-wrappers projects are set up.

  • Lack of ergonomic interop: Using Kotlin from Java is pretty straightforward; sometimes you need an annotation like @Throws or @JvmStatic but most of the time, it's very neat. Using Kotlin from JS is impossible without bridging code.

    This was very valid a couple years ago. I can guarantee that things have improved quite a bit. What we are still missing is:

    1. Exportability of class properties without accessors, so that objects can be naturally (de)serialized KT-17683
    2. Exportability of unsigned numbers and primitive arrays KT-51389
    3. Exportability of coroutine "primitives" like Channel and Flow KT-80733
    4. Exportability of dependencies via DSL (where @JsExport cannot be manually added) KT-47200

And btw, if you want to know which flags the K/JS compiler supports, go look at K2JSCompilerArguments.kt.


r/Kotlin 6d ago

I got Compose Multiplatform running on Apple tvOS, published on Maven Central, one line in settings.gradle.kts

Post image
Upvotes

JetBrains doesn't ship tvOS artifacts for Compose Multiplatform, and the issue asking for it (CMP-5686) has been open for a while. I needed it for my own TV app, so I built the port and published it. It's a community project, not official. I posted it in Kotlin Slack #compose-ios earlier this week; this is the longer writeup.

Using it

// settings.gradle.kts
plugins {
    id("dev.sajidali.compose-tvos") version "1.3.0"
}

Add tvosArm64() / tvosSimulatorArm64() to your KMP module and leave your dependencies alone: org.jetbrains.compose.*, androidx.tv:tv-material, Koin, Coil 3, all stock coordinates. Kotlin 2.3.20+.

How it works (the part I think is interesting)

It's not a hard fork you point your build at. The settings plugin registers a component-metadata rule that, at dependency-resolution time, attaches tvOS variants to the official Compose modules and points them at tvOS-enabled builds published under dev.sajidali.* on Maven Central. Every other target keeps resolving the official JetBrains artifacts byte-for-byte, and it's official-first: if a module already ships tvOS klibs upstream (compose.runtime, koin-core, lifecycle...), the plugin leaves it alone. So when JetBrains eventually ships tvOS for a module, the plugin steps aside for that module with no change on the consumer side. It also intercepts the org.jetbrains.compose Gradle plugin marker so compose.material3 etc. resolve correctly.

The fork itself (compose-multiplatform-core with tvOS as a Kotlin/Native target) is where most of the work went:

  • A tvOS UIKit scene stack sharing the iOS FrameChoreographer architecture
  • Siri Remote input: D-pad focus traversal, swipe-to-focus, Menu mapped to Key.Back, and telling a clickpad press apart from a swipe by hardware timestamp
  • 10-foot density (Compose's default density on a 4K TV is unusable), on-demand keyboard for text fields, focus restoration when dialogs close
  • androidx.tv:tv-material ported to Compose Multiplatform with a tvOS source set
  • A real tvOS build of window-core so material3-adaptive works without stubs
  • Koin (koin-compose, koin-compose-viewmodel) and Coil 3 with tvOS targets, since neither ships them

Proof

The GIF is Google's JetStream TV sample (all screens, D-pad focus, theming, AVPlayer on tvOS / ExoPlayer on Android TV behind one interface) on the Apple TV 4K simulator, built from Maven Central + the Plugin Portal with nothing published locally. The same toolchain builds a production TV app of mine with zero app-source changes.

Honest limitations: no automated tvOS tests yet; tvosX64 isn't built; I republish roughly once per Compose stable line (currently 1.12.0), as far as my own app needs. PRs welcome.


r/Kotlin 5d ago

Stopped hardcoding dp breakpoints for WindowSizeClass — modeled it as a sealed class instead

Upvotes

Every "responsive Compose UI" example I found for WindowSizeClass ends up as a wall of if (width < 600.dp) ... else if (width < 840.dp) ... scattered across every screen. Refactored ours into a sealed class hierarchy (Compact/Medium/Expanded, each carrying its own layout params) and just pattern-match on it with when at the composition root instead.

Ends up much easier to test too — you can construct each sealed subtype directly in a Compose UI test without faking window metrics.

Wrote up the full pattern, including how it plays with foldables and the hinge/posture APIs (which is where it gets genuinely annoying): Please check comment ⬇️

Curious if anyone's modeled this differently — especially interested if people are handling the posture APIs with a different state shape.


r/Kotlin 5d ago

i want to wipe the terminal after asking questions

Upvotes

So I'm new to Kotlin, and I'm trying to make a guess the number game for terminal and I wanted to make a little text intro and clear the console after, but I haven't been able to do it. I found some ways to do it but not as I want to.

Because when you do the clear command in the terminal, it wipes the whole thing, and you can't go back, but when I use commands like "print("\u001b[H\u001b[2J")" or "System.out.flush()" it is like it just pushes the view down; it doesn't really wipe the terminal, it just pushes the text up.

I wanted to know how or if it's even possible to do it. I don't know if it might be helpful, but I use Windows 11 and PowerShell (7.6.5).

Also, in another topic, I want to hear if there's a better way to try my code because right now I'm doing kotlinc .\file-name.kt -include-runtime -d .\file-name.jar && java -jar .\file-name.jar


r/Kotlin 6d ago

Elegance never goes out of style — iterating LocalDate with Kotlin ranges

Thumbnail gallery
Upvotes

Sometimes Kotlin just feels unfairly nice.

```kotlin

val start = LocalDate.of(2024, 1, 31)

val end = LocalDate.of(2024, 12, 31)

for (date in start..end step Period.ofMonths(1)) {

println(date)

}


r/Kotlin 5d ago

A Kotlin Result<T, E> that also tells you the kind of success and failure

Upvotes

Hey everyone, following up on kiit-codes from a couple weekends ago, a small taxonomy for classifying success and failure into named groups (Succeeded, Restricted, Invalid, Rejected, ...) instead of a boolean or a numeric code.

I've now published kiit-result, a Result<T, E> type built directly on that same taxonomy, to tell you the kind of success or failure.

Most Result/Either types treat success as just a value. There's no separate classification for whether it completed normally, is still pending, or was intentionally excluded (a duplicate, for example). On the failure side, E usually carries the error details, but there's no independent classification of the kind of failure.

Here, both branches carry a status:

  1. Success<T> : Gets a Passed group (Succeeded/Pending/Excluded/Information)
  2. Failure<E> : Gets a Failed group (Restricted/Invalid/Rejected/Unserved)
  3. Separation: T holds value, E holds error, and Status holds the kind of success or failure
Kiit Result<T,E> that also tells you the kind of success/failure

A few things worth knowing:

  1. Status on both branches: the primary differentiator from other Result/Either types, status classifies the kind of success or failure independent of T/E, most libraries only give you that on the failure side, if at all.
  2. Progressive Adoption : Status is optional to manage in daily use. Builders (see below) apply sensible defaults, so you only need to pick one when you want the extra precision.
  3. Type Aliases: E stays fully generic, Outcome<T>, Try<T>, Option<T>, and Validated<T> are just aliases on one Result<T, E>, not four separate types.
  4. Builders: restricted()/invalid()/rejected()/unserved() pre-populate the matching status via sensible defaults, so it's rarely built by hand.
  5. Familiarity: map, flatMap, fold, ... isn't novel, most of it has precedent in Rust, Swift, Kotlin libraries. The actual bet is the taxonomy fused onto both branches, not new operators.
  6. Action: either branch can also carry optional context, recording which operation produced a result, for debugging across nested or chained calls.implementation("dev.kiit:kiit-result:1.0.2")
  7. GitHub: github.com/kiitdev/kiit-result
  8. Docs: kiit.dev/docs/kiit-result
  9. Tutorial: kiit.dev/docs/kiit-result#tutorial

Curious what people think. Happy to answer questions in the comments.

Thanks!

Edit: Updated diagram


r/Kotlin 7d ago

ReqLab (Open-source Desktop API Client) now features full MCP Client support and JSON5 out-of-the-box! Looking for your feedback

Post image
Upvotes

Hey everyone,

If you haven’t seen it before, ReqLab is an open-source, desktop-first API client I built with Kotlin and Compose Multiplatform. It runs completely offline with no cloud lock-in, no telemetry, and no mandatory accounts. It’s built to be a fast, scriptable alternative to tools like Postman or Insomnia.

I just rolled out a major update introducing two heavily requested features:

🤖 Full MCP (Model Context Protocol) Client Support

ReqLab is now a full-fledged MCP client that lives right inside your REST workspace!

  • Integrated Workflow: Your MCP items sit side-by-side with your REST requests, sharing the same collections, environments, and response panes.
  • Rich Features: Supports tools (Form/JSON), resources (read + subscribe), and prompts. You also get an Activity JSON-RPC inspector to see exactly what’s going on under the hood.
  • Multiple Transports: Works with streamable HTTP, desktop stdio, legacy HTTP+SSE, and auto fallbacks.
  • Variables Everywhere: Interpolate your {{variables}} directly in URLs, commands, headers, and auth (which uses the exact same editor as REST).

📝 JSON5 Authoring by Default

Working with raw JSON bodies can be a headache when you just want to leave a comment or drop a trailing comma.

  • I've updated Compose-native code editor to support JSON5 authoring by default for your request bodies.
  • You get syntax highlighting, comments, and unquoted keys in your editor, and ReqLab will automatically convert it back to strict JSON when the request is sent over the wire.

I’d love your feedback!

I'm constantly trying to improve the developer experience and make this the best offline API client out there.

If you get a chance to clone the repo and run it (or download the latest release), I want to hear from you!

  • How does the MCP workflow feel alongside your traditional REST requests?
  • Are there any specific MCP tools or edge cases you’d like to see better supported?
  • Any UI/UX rough edges you ran into while editing JSON5 payloads or setting up scripts?

Check out the repo here: https://github.com/snj07/req-lab


r/Kotlin 7d ago

Would you rewrite a small Kotlin library in Java just to avoid stdlib ?

Upvotes

I moved the core of a small library from Kotlin to Java mainly to avoid pulling kotlin-stdlib into pure Java applications.

After doing it with Java I ended up adding JSpecify, @NullMarked, nullable annotations, and extra Kotlin compatibility tests just to preserve the same nullability behavior.

For a small JVM library meant for both Java and Kotlin users, would you keep the core in Kotlin and accept kotlin-stdlib as a dependency?

Or do you think keeping the core Java-only is worth the extra work?


r/Kotlin 9d ago

Kotlin/Native desktop still doesn’t feel productive for real applications

Upvotes

I've been trying to use Kotlin/Native for desktop/CLI stuff, and honestly it feels like you get a native binary and then you're on your own. Basic things like logging, resource loading, decent file operations, and process execution are either missing or surprisingly painful. kotlinx-io helps, but it's still pretty limited. There are third-party libraries for some of this, but a lot of them are incomplete, barely maintained, or just don't cover what you actually need. Meanwhile in Rust/Go/Graal NativeImage, this is boring standard-library stuff and you can just get on with building your app. Kotlin/Native is cool technology, but for desktop development it still feels like an unfinished platform rather than something you'd actually choose for getting work done. Would be nice if JetBrains spent some time on the boring fundamentals that make a platform actually usable.


r/Kotlin 9d ago

Google Maps vs Mapbox for Android navigation?

Upvotes

Hey guys, I’m building an Android app that needs map + navigation, custom map icons, and support for multiple transportation modes (car, bike, walking, etc.).

Would you recommend Google Maps, Mapbox, or something else? Any experience with these would be appreciated!