Kotlin is the language Google decided Android should be written in. JetBrains designed it as a less-painful alternative to Java that compiles to the JVM bytecode the rest of the Java ecosystem speaks; first stable release was Kotlin 1.0 in February 2016; Google made it an official Android development language at I/O 2017 and a first-class language for Android in 2019. The current stable as of 2026 is Kotlin 2.x (the K2 compiler shipped with Kotlin 2.0 in May 2024). Most new Android applications written since 2019 are predominantly Kotlin; the operator who reverse-engineers Android malware or commercial apps is reading Kotlin much more often than reading Java now.
For the operator, Kotlin earns its place mostly through Android. The general-purpose “let’s write our pen testing tools in Kotlin” pitch never really took because Python is faster to write and runs on every operator host without a JVM install. Kotlin’s actual operator value is reading other people’s compiled Kotlin (decompilation via jadx, IDA Pro, Ghidra, all of which have improved Kotlin support significantly since 2020), writing Android instrumentation and Frida scripts that target Kotlin internals, and building Mobile-Application-Security-Testing (MAST) tooling that has to integrate with the Android build system. This walkthrough covers enough Kotlin syntax to read other people’s code, then the offensive contexts where Kotlin actually shows up.
Language background#
Kotlin came out of JetBrains’ frustration with Java. The company wrote IntelliJ IDEA (and most of its other products) in Java; their developers wanted features Java didn’t have, didn’t want to switch to Scala (which they considered too academic), and decided in 2010 to start their own JVM language project. The first public release was July 2011; 1.0 stable in February 2016; 2.0 with the rewritten K2 compiler in May 2024.
Kotlin’s value proposition is pragmatic: 100% Java interop (you can call Kotlin from Java and Java from Kotlin without ceremony), modern language features (null safety, data classes, extension functions, sealed types, lambdas, coroutines), and a much terser syntax than Java for the same operations. The K2 compiler in Kotlin 2.0 made the compile times competitive with Java’s for the first time, which removed one of the historical complaints.
Kotlin targets multiple platforms:
- Kotlin/JVM: the default, runs anywhere a JVM runs.
- Kotlin/Android: a specific JVM variant with Android-specific tooling, the primary deployment target since 2017.
- Kotlin/JS: transpiles to JavaScript for browser or Node.js work. Less popular than the JVM target.
- Kotlin/Native: compiles to native binaries via LLVM, targeting iOS, Linux, Windows, macOS. Used heavily for Kotlin Multiplatform Mobile (KMM).
- Kotlin/Wasm: WebAssembly target, still experimental but maturing.
Kotlin Multiplatform Mobile reached Stable in November 2023, which means iOS and Android apps can now share business logic written in Kotlin while keeping platform-specific UI in Swift and Jetpack Compose. KMM adoption has grown steadily; not yet the default for cross-platform mobile but a credible alternative to React Native and Flutter.
Language basics#
The features the operator should know to read Kotlin in the wild:
Null safety#
Kotlin’s type system distinguishes nullable from non-nullable references at compile time. A type with a ? suffix can hold null; a type without can’t. The compiler rejects null dereferences at compile time, which kills the entire NullPointerException class of bugs that haunts production Java.
var s: String = "Hello"
// s = null // compile error
var maybe: String? = "Hello"
maybe = null // legal
// Safe-call operator: returns null instead of crashing
val length: Int? = maybe?.length
// Elvis operator: default if null
val safeLength: Int = maybe?.length ?: 0
// Not-null assertion (use sparingly)
val forcedLength: Int = maybe!!.length // throws NPE if nullThe !! operator is the explicit “I assert this is non-null” escape hatch. Every !! in production Kotlin is a potential NPE waiting for the wrong input; experienced Kotlin code avoids them except where the type system genuinely can’t prove non-nullness.
Extension functions#
Extension functions add new methods to existing types without modifying or subclassing them. The compiler resolves the call statically at the call site; under the hood, an extension function compiles to a static method that takes the receiver as its first parameter.
fun String.removeWhitespace(): String = replace(" ", "")
fun main() {
println("Hello, World!".removeWhitespace()) // "Hello,World!"
}Used heavily in Kotlin standard libraries and Android extensions (androidx.core.ktx). When you’re reading decompiled Kotlin, expect to see extension functions named in the form Receiver$method or similar mangled names.
Lambdas and higher-order functions#
Lambdas are first-class values. A function that takes a lambda is a higher-order function. The single-parameter lambda gets the implicit name it; multi-parameter lambdas need explicit names.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
val squares = numbers.map { it * it } // [1, 4, 9, 16, 25]
val sum = numbers.reduce { acc, n -> acc + n } // 15
// Trailing-lambda syntax: when the last parameter is a lambda, it can go outside the parens
val pairs = numbers.zip(numbers.reversed()) { a, b -> a to b }
}The standard library’s collection operations (filter, map, reduce, fold, groupBy, flatMap) lean heavily on lambdas. Once you’ve written Kotlin for a few days, the Java-style explicit-iteration patterns start looking unnecessarily verbose.
Coroutines#
Coroutines are Kotlin’s answer to async programming. A coroutine is a suspendable computation that can yield and resume without blocking a thread. The kotlinx.coroutines library (1.0 stable in October 2018) provides the standard runtime.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
delay(1000)
println("World!")
}
println("Hello,")
job.join()
}launch starts a coroutine. delay is the coroutine-aware sleep (doesn’t block the thread). runBlocking bridges the synchronous main function to the coroutine world. job.join() waits for the coroutine to complete.
Coroutines are the substrate Android’s modern async patterns are built on. If you’re reverse-engineering modern Android apps that do networking, expect to see suspend functions everywhere.
Where Kotlin actually shows up on engagement#
The honest framing: Kotlin’s primary operator-relevance is reading other people’s Android code. The general “let’s write our pen testing toolkit in Kotlin” use case never took because Python is faster to write, Go is better for cross-platform implants, and the operator host doesn’t want a JVM dependency. Kotlin’s actual operator use cases:
- Android app reverse engineering. Most Android apps written since 2019 are Kotlin or Kotlin-first. Decompiling them produces Kotlin source (jadx 1.5+, Ghidra with the Kotlin metadata plugin, Bytecode Viewer). Reading the result requires being able to parse Kotlin syntax fluently.
- Frida scripts targeting Kotlin internals. Frida’s JavaScript runtime can hook into Kotlin-compiled methods just as it can hook Java methods, but the name mangling Kotlin produces (companion objects, coroutine suspension points, inline-function expansion) means the operator needs to know what the source-level Kotlin looked like to find the right hook target.
- Custom Android instrumentation. Writing test harnesses or modified versions of Android apps for dynamic analysis. The build tooling (Gradle plus AGP) is Kotlin-aware and Kotlin DSL is now the preferred way to write Gradle build scripts.
- Mobile-app pentest tooling. MobSF, Drozer, and similar tools have Kotlin-aware analysis modules; writing custom analyzers for these is sometimes easier in Kotlin than in Python because of the type-system support for parsing Java/Kotlin bytecode.
The general-purpose “Kotlin port scanner” examples that older posts show are technically valid Kotlin code, but they’re really Java standard library code written with Kotlin syntactic sugar; there’s nothing about them that wouldn’t work the same way in Java. They’re shown below because they’re useful as syntax examples, with the caveat that an operator who actually needs a port scanner reaches for Nmap, not for a Kotlin compile.
Port scanner (syntax example)#
Standard java.net.Socket access from Kotlin syntax. Demonstrates collection ranges, exception handling, and the for-loop form.
import java.net.*
fun main() {
val address = InetAddress.getByName("127.0.0.1")
for (port in 1..65535) {
try {
val socket = Socket()
socket.connect(InetSocketAddress(address, port), 1000)
println("Port $port is open")
socket.close()
} catch (e: Exception) {
// do nothing
}
}
}The for-loop iterates over the 1..65535 range; the try/catch handles the connection-refused exceptions silently. In actual operator work, this would run sequentially and take hours; a real Kotlin port scanner would use coroutines to parallelize, but at that point you might as well use Nmap.
Hash cracker (syntax example)#
Dictionary attack against an MD5 hash. Demonstrates file I/O, the standard library’s MessageDigest access, and string formatting.
import java.io.File
import java.security.MessageDigest
fun main() {
val passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99" // MD5 hash of "password"
val wordList = File("wordlist.txt").readLines()
for (word in wordList) {
val hashedWord = hashString(word, "MD5")
if (hashedWord == passwordHash) {
println("Password found: $word")
return
}
}
println("Password not found")
}
fun hashString(input: String, algorithm: String): String {
val bytes = MessageDigest.getInstance(algorithm).digest(input.toByteArray())
return bytes.joinToString("") { "%02x".format(it) }
}For anything resembling production hash cracking, the answer is hashcat on a GPU rig, which is several orders of magnitude faster than any CPU-bound JVM code. The Kotlin version above is useful for understanding the algorithm, not for actually cracking hashes.
Web crawler with jsoup (syntax example)#
HTML parsing via the jsoup library, which is a Java library Kotlin uses unchanged. Demonstrates dependency interop and CSS-selector traversal.
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
fun main() {
val url = "https://example.com"
val document = Jsoup.connect(url).get()
val links = document.select("a[href]")
for (link in links) {
println(link.attr("href"))
}
}jsoup is the standard JVM HTML parsing library. The Kotlin call site is essentially identical to the Java equivalent; the value Kotlin adds is the terser collection iteration.
Kotlin compared to the other operator languages#
How Kotlin stacks up for the work an operator actually does:
| Aspect | Kotlin | Java | Python | Go |
|---|---|---|---|---|
| Primary operator use case | Android RE, Android app instrumentation | Same (older Android apps) | Operator-host scripting | Cross-platform implants |
| Reading other people’s code | Required for modern Android | Required for older Android, enterprise | Required for nearly everything | Required for some C2 frameworks |
| Quick scripting on operator host | Awkward (JVM startup, Gradle deps) | Awkward (same) | Excellent | Decent (single binary) |
| Cross-platform binary deploy | Possible via Kotlin/Native; not common | JVM-only without GraalVM | N/A (interpreted) | Trivial |
| Null safety | Built-in via type system | None (pre-Java-25 patterns help) | None | None (zero values, panics) |
| Coroutines / async | First-class, structured concurrency | Threads + virtual threads (Java 21+) | asyncio | Goroutines, channels |
| Android first-class | Yes (Google default since 2017) | Yes (legacy default) | No | No |
| Static analysis ecosystem | IntelliJ + Detekt; mature | IntelliJ + SpotBugs + Error Prone | mypy + pyflakes; ok | go vet + staticcheck; excellent |
| Operator-side toolkit ecosystem | Small | Moderate (Burp extensions, JD-GUI) | Largest | Growing (Sliver, many implants) |
| Learning curve from Java | Trivial | N/A | Trivial | Moderate |
The practical takeaway: Kotlin is the right language when the work is Android, the wrong language for almost everything else operator-relevant. If you’re going to do Android pentest or RE work, learn Kotlin. If you’re not, you can get away without it.
Specific tooling worth knowing for the Android operator:
- jadx (Skylot): the standard Android decompiler. Reads APKs, decompiles Dalvik bytecode back to Java or Kotlin source. Modern versions handle Kotlin metadata better than they did a few years ago.
- Ghidra: NSA-developed reverse engineering platform; has Android-specific plugins and the kotlin-metadata reading support that lets it produce sensible Kotlin decompilation.
- MobSF (Mobile Security Framework): static and dynamic Android analyzer; understands Kotlin-specific constructs.
- Frida: dynamic instrumentation. The Frida-JavaScript bindings to hook Kotlin methods are the same as for Java methods, but the operator needs to know what the compiled Kotlin names look like.
- APKLab (VSCode extension): integrated environment for Android RE with decompilation, manifest editing, and rebuild support.
- Quark Engine: static analyzer for Android malware behavior.
What this comes down to#
Kotlin earned its place in operator tooling through Android, not through general-purpose pen testing. The language is genuinely nicer than Java, the null-safety story is a real win over Java’s nullability situation, and the coroutines model is a credible alternative to anything Python or Go offers for async work. None of that makes Kotlin the right answer for the operator-host scripting and cross-platform implant work that most engagements actually need.
If you’re doing Android RE in 2026, Kotlin is non-optional. Learn enough to read what jadx produces, understand the coroutine and extension-function patterns, and recognize the standard library idioms. That’s the bar for the work; the rest is whatever Java background you already have plus the syntactic patterns above.
For everything else operator-relevant, the Python post and the Go-versus-Rust discussion in the C post cover what most engagements use.