Kotlin stamps the same beginner label on three runtimes that are not one sitting. An Android leaf that needs an emulator or a device. A KMP ticket that lives in the shared module. A Ktor or Spring-Kotlin-ish library that is just a JVM process. Same badge. Different boot. The Kotlin hub is one feed; you still pick the runtime you can actually start tonight.
Generic hunting: how to find good first issues. Multi-language picking: best open source projects for beginners. Merge checks: how to pick an issue that will get merged. This URL is the Kotlin filter.
Open the Kotlin hub
One feed: labeled good-first and help-wanted issues on active GitHub repos whose language is Kotlin. Android apps, KMP modules, and JVM libraries sit next to each other. On 2026-08-31: about 685 labeled issues at 10+ stars. Snapshot. It moves. The list does not tell you which runtime you can boot.
Not writing Kotlin? The homepage is the language switch.
Open a ticket. Read the body. Skip stale, claimed, and repos that have not merged lately. I am not listing famous Android or KMP names. A leftover label on a well-known app is still leftover. The feed is the hub, ticket by ticket.
Android: boot the app, then a leaf screen
If the ticket is Android, the first sitting is Gradle plus an emulator or a physical device. ./gradlew :app:assembleDebug (or the task CONTRIBUTING names) has to produce something you can launch. Then the change is a leaf: one XML layout, one Compose screen, a string the issue quoted. Not the navigation graph. Not the theme. Not a rewrite of XML into Compose because you prefer it.
Skip this ticket if you cannot boot the app tonight. No emulator image, no device, a Gradle sync that never finishes — that is two projects. Take a KMP shared-test ticket or a JVM library instead. Do not plan to “figure out Android Studio later” on issue one.
Out of scope until a later PR:
- Android framework internals (platform
android.*, lifecycle guts the rest of the app already trusts) - Gradle plugin core, AGP internals, custom
buildSrcplugins - A whole-module Compose migration
- Bumping AGP, the Kotlin Gradle plugin, or the compile SDK while you are here
The mergeable first Android PR is a leaf screen you can screenshot. The framework waits.
KMP: shared tests or docs, not expect/actual plumbing
Kotlin Multiplatform puts two kinds of files under one label. They are different tickets.
Shared module. commonMain / commonTest. A test that runs with the shared check task they already use. A KDoc example on the shared API the issue drafted. A README snippet that actually compiles against that source set. That is issue one.
Platform actuals. androidMain, iosMain, jvmMain actual implementations. New expect wiring. CocoaPods or XCFramework packaging. Those review like architecture even when the badge says beginner. First PR is a leaf in the shared tests or docs, not expect/actual plumbing.
Do not take an expect/actual hole as issue one because you “know Kotlin.” Shared tests do not need a device. Actuals often do. If you cannot run the iOS actual tonight, you cannot finish that ticket tonight.
I am not listing famous KMP names. Filter the Kotlin hub and judge the source set, not the brand.
Backend: one test in a small library, not the engine
Ktor-shaped and Spring-Kotlin-ish tickets share the hub with Android. The runtime is a JVM process. No emulator. That is the only reason they look easier — and only if the surface is small.
The mergeable first backend PR is one test or docs in a small library. Same test source set they already have. Kotest if the Gradle file already pulled it. JUnit if that is the suite. One method. Assert the behavior the issue described.
It is not:
- The Ktor engine, plugin registry, or CIO internals
- Spring-Kotlin-ish container or routing guts
- Adding Kotest to a JUnit tree (or the other way) as a side quest
- A whole-module coroutines rewrite
A labeled ticket in a huge framework is not the same size as a labeled ticket in a three-module library. Prefer the smaller surface for issue one. After a merge in a small lib, issue two can be a framework leaf. Not before.
ktlint and detekt: the hunk
ktlint will rewrite the file because the editor offered. detekt will open findings you did not cause.
Rules:
- ktlint / detekt only the lines you changed
- If CONTRIBUTING says run ktlint before push, run it, then restore files the issue did not name
- Never open a whole-module ktlint PR
- Leave
.editorconfig, ktlint Gradle config, anddetekt.ymlalone unless that file is the ticket - Pre-existing detekt warnings on lines you skipped stay
- Do not enable a new detekt rule you like locally
A formatted test is hygiene. A module-wide ktlint is a closed PR.
Android internals and Gradle plugin core wait
Platform android.* code, AGP / Gradle plugin core, and the insides of a Ktor or Spring-Kotlin engine look labeled and are still issue two. Those files have implicit contracts. The badge means they want help. It does not mean the patch is first-timer sized.
Issue one is a leaf: an XML or Compose screen you can boot, a shared-module test, docs in a small JVM library — not the framework, not the plugin.
After a merge in that repo, you can go inward.
Ship one ticket
- Filter on the Kotlin hub. Drop stale and claimed. Confirm recent merges. Pick Android, KMP, or backend — the runtime you can boot tonight.
- Read
CONTRIBUTING.mdfor the Gradle task, the emulator or device note, and ktlint/detekt. Get default branch green, or the app actually launched. - Comment on one issue.
- Diff stays on the ticket. Leaf screen, shared test, or small-library test/docs. Hunk-only ktlint/detekt. No AGP or Kotlin plugin bump.
Fixes #123. Say which command you ran. Stay for review.
Git: first pull request guide. Week-1: how to contribute to open source.
Related
- Java good first issues
- PHP good first issues
- How to find good first issues
- Best open source projects for beginners
- How to pick an issue that will get merged
FAQ
Android, KMP, and backend all say Kotlin. Can I pick any of them?
No. They are different first evenings. Android needs an emulator or device. KMP shared tests do not. Backend is a JVM library. The Kotlin hub is one feed; you still pick the runtime you can boot.
I cannot start an emulator tonight. Is an Android ticket still issue one?
No. Skip it. A leaf XML or Compose screen you cannot launch is not reviewable. Take a shared-module test or a small JVM library instead.
Can my first KMP issue be the expect/actual for a new platform?
No. First PR is a leaf in the shared tests or docs. expect/actual plumbing, iOS actuals, and packaging are a different ticket. After a merge in that repo, issue two can move to actuals.
Should I run ktlint or detekt on the whole module?
No. Format the hunk you touched. A whole-module ktlint / detekt rewrite is a style diff. Leave detekt.yml alone unless that file is the ticket. If CI formats the module, let CI.
Where do I find live Kotlin good first issues?
On the Kotlin hub. Filter, skip stale and claimed threads. Read CONTRIBUTING for Gradle, the runtime (Android / KMP / JVM), and ktlint/detekt. Other languages: the homepage.
Start here
Open the Kotlin hub. Pick one unclaimed ticket on the runtime you can boot: an Android leaf screen, a KMP shared test or docs, or one test/docs in a small Ktor/Spring-Kotlin-ish library — not Android framework internals, not Gradle plugin core, not expect/actual plumbing. Comment once, ktlint only what you touched, keep the diff on that job, and stay for review. Not writing Kotlin tonight? The homepage is the language switch.