youndie@kotlin.website:~$ ls -lt blog/

blog

Notes on Kotlin, Kotlin/Native, and whatever I'm currently building.

Instrumentation PGO on Kotlin/Native

A profile can be taken and applied on stock tools, with no fork. Turning it into a binary means going around the compiler's own LTO, which costs nothing measurable on a benchmark — and the profile returns 10–13 % there. What that leaves unanswered for a service, and the recipe.

A Kotlin server that ships twice, in an hour

Clone a template, rename it, and you have a service that compiles to a native Linux binary and a JVM distribution from one source: two probes-and-shutdown halves that behave the same, a 14 MB image, and 3 min 48 s from git clone to both answering. Every number here was measured on a machine that had never seen the repository.

Dispatcher parallelism and the memory of a Kotlin/Native service

Resident memory on Kotlin/Native is the thread count times half a megabyte. A forked Ktor CIO on a fixed thread pool holds 200 connections on eight threads and 16 MB — fewer threads and a lower p99 than Go on the same machine.

Ktor on Kotlin/Native under a container limit

A Ktor service that was OOM-killed in every run at 128 MiB now serves 2 000 rps there in 61 MB at a p99 of 5.5 ms. Two changes, neither of them a GC setting.

Kotlin/Native in `FROM scratch`: 593 KB for a hello-world, 9.5 MB for a Ktor service

A statically linked Kotlin/Native binary that starts in an empty image and still resolves hostnames, the exact flags that produce it on a stock 2.4.10, what it costs in size, RSS and cold start — and the four files a Ktor service has to put back, because glibc dlopens its character-set converters.

What is in a Kotlin/Native binary, and where its time goes

One symbol table answers both questions. Every expectation I brought to it was wrong: the runtime is a 40 KB constant, a fifth of the file is symbol names, most of a release binary is not Kotlin, and a sampling profiler written in Kotlin hangs the process it profiles.

Where Kotlin/Native and the JVM actually differ

129 probes run on both runtimes: what agrees exactly, the seventeen rows that do not, why none of those seventeen has ever caused an outage, and why the ones that did are harder to test for.

Handing a request to a handler costs 39 % of a Ktor CIO service's CPU — and 1 % on Netty

The same service on CIO, Netty and Jetty, one -D apart: 166, 82 and 146 microseconds of CPU per request. A third of CIO's CPU is the queue inside Dispatchers.IO; one system property removes most of it and costs 7.7x on a handler that blocks. The stand, the numbers and the limits.

CRaC on a Ktor service with HikariCP and Exposed: every socket that refuses the checkpoint

A CRaC checkpoint of a real Ktor service restores in 131 ms against 2 317 and its JIT is warm. Getting there means every open socket, and HikariCP names its own. What breaks, in the order it breaks, with the logs.

Leyden AOT cache for a plain Kotlin/JVM service: what it gives, what it costs, and the three things Spring does for you that you'll do yourself

How to give a Ktor, http4k or any application-plugin service a Project Leyden AOT cache on JDK 25: what the cache is and is not, what it did to a real service's cold start and memory, the training, verification and packaging that Spring Boot and Quarkus hide, the same by hand in five steps, and through a Gradle plugin.

User code is 1–4 % of a Ktor service's CPU: a negative result for a bytecode optimiser

I set out to build a Kotlin/JVM optimiser plugin and measured first. User code owns 1–4 % of CPU and 3–10 % of allocations on a Ktor service, R8 cannot even run as the baseline on this stack, and the plugin was not built. The numbers and the stand.

OpenJDK 25.0.0–25.0.3 uses a stale AOT cache without saying so

On JDK 25 before 25.0.4 and JDK 26 before 26.0.2 the JVM does not compare an AOT cache with the jars it was trained against: replace a jar, keep the cache, and the old classes run. The experiment, the logs, and what to check yourself.

kapkan: ktlint rules with a defect behind each one

A ktlint rule set with no configuration, where every rule encodes one class of defect this stack paid to find — and the two that did not survive being measured.

A screen editor for backend-driven UI

kompot-studio opens a screen body beside the renderers that will draw it: a tree, a JSON editor, a schema-driven inspector, a preview in the consumer's own brand frame, and findings from schema, rules, vocabulary and the render itself.

I liked Metro, so I went and read its sources

How kvadrant-ui got built: a design language I missed, the Microsoft files that turned out to still be readable, and the one effect I built from a name instead of a document.

Backend-driven UI for Kotlin Multiplatform

kompot: the server describes a screen as a tree of components, the client renders it, and a new screen ships without a client release. Open hierarchies, KSP registration, and two conformance kits.

Silence is not a clean run

Durability that 779 tests could not see, a fuzzer that needed two files to get through the door, and a harness that reported targets it never ran.

Screenshot fixtures declared with @Preview

Since 0.3.0 viddik reads androidx.compose.ui.tooling.preview.Preview, so one annotation serves the IDE preview pane, Android's screenshot tooling and the golden-file suite at once.

Server-side rendering for Kobweb: what is reachable from outside

Kobweb has no server-side rendering and its issue for one has been open since 2022. This is an investigation into how much of it can be built from outside the framework, what it costs, and which part turns out to be impossible without a change in Compose HTML.

An identity provider for 46 MiB

What a platform actually needs from an IdP, what that costs when it ships as one native binary, and where the measurement stops being a comparison.

Green is not the same as checked

A lock that only ever answered yes, an out-of-scope label with eight defects behind it, and a benchmark the optimiser deleted.

docs-bootstrap: a documentation format with a gate

Documentation formats for coding agents are written as prose rules that nothing verifies. docs-bootstrap is the same idea with checks that can fail, run by CI against the repository's own example.

The path you signed is not the path you sent

Writing an S3 client for Kotlin/Native. Three things that only turned up once a real request went out, and one that a green test suite was hiding.

Kotlin/Native under load, and what I measured wrong

28 ms to first response and 20 MiB idle. Then the load arrived — and three of my own measurements turned out to be wrong.

The bugs that don't fail

294 tests, 71 specification scenarios, and four bugs that only a real mongod would show — because none of them threw anything.

Logs shaped for an agent to read

Handing a coding agent a dashboard link is handing it nothing. tracy makes MCP the primary read path — and that single decision changes what the storage has to store.

A broker that fits in one head

booblik reproduces what makes an append-only log fast and leaves out everything that makes Kafka a cluster — including three features that are incompatible with zero-copy by construction.

Percentiles don't average

metrik costs the monitored service 106 nanoseconds per request and a UDP packet a minute — and the reason it merges percentiles instead of averaging them is that averaging them is simply wrong.

What is left in the system if you die halfway

A saga engine built around one question. Steps as interceptors, compensation in reverse, and an outbox that makes "the work happened but the notification never went out" structurally impossible.

Writing SMTP from the RFC instead of around it

A Kotlin/Native service has two ways to send mail — shell out to sendmail, or wrap libcurl. Both hide the protocol exactly where it has to be visible.

The screenshot in the README is the test

mani is a Kotlin Multiplatform demo where the same server compiles to the JVM and to a native binary — and where the picture advertising the interface cannot drift from it.

A Telegram bot is a state machine pretending not to be

telek models a conversation as pure transitions with effects on the side — which is what makes a wizard resumable, testable and portable between two transports.

A crash tracker that fits on one box

katcher — Kotlin/Native, Ktor and HTMX, in one binary small enough that nobody has to justify running it.

Screenshot tests without an emulator

viddik renders Compose Multiplatform through a real Compose Desktop window instead of LayoutLib, so goldens stay the same on macOS, Linux and Windows.

Ask the desktop, don't guess it

A custom title bar for Compose Desktop has to know which side the buttons go on. On Linux there is no answer — so appframe asks GNOME directly.