Lightweight Unicode code-point handling for Kotlin Multiplatform β the Character API you've been missing in common code, without depending on ICU.
Kodepoint brings Unicode Character Database queries β case conversion, character classification, script and category lookup, and correct code-point iteration β to every Kotlin target through a single, allocation-free Codepoint value class.
val emoji = Codepoint(0x1F600) // π
emoji.isLetter() // false
emoji.getUnicodeScript() // COMMON
Codepoint('A'.code).toLowerCase().asString() // "a"Kotlin's common standard library has no equivalent of Java's Character. In shared code you can't ask whether a character is a letter, uppercase it, or safely walk a string by code point β and Kotlin's Char is only 16 bits, so emoji, CJK extensions, and other supplementary characters (anything above U+FFFF) silently break naive per-Char logic.
Kodepoint fills that gap:
- π¦ Works everywhere β one API across JVM, JS, WasmJS, iOS, macOS, watchOS, tvOS, Linux, and Windows. Write your text logic once in
commonMain. - π§© Full Unicode range β first-class support for supplementary code points and surrogate pairs, so π and π are handled correctly.
- β‘ Zero-allocation β
Codepointis a@JvmInline value classwrapping a singleInt. No boxing, no wrapper objects on the hot path. - β
JVM-validated correctness β non-JVM results are checked against
java.lang.Characteracross all 1,114,112 code points (see JVM Compatibility). - πͺΆ No runtime dependencies β pure Kotlin plus compact generated lookup tables. On the JVM it delegates straight to
java.lang.Character. - π Benchmarked β performance tracked over time on a public dashboard.
Note: This project is a stopgap until KT-23251 (Extend Unicode support in Kotlin common) and KT-24908 (CodePoint inline class) land in the Kotlin standard library.
Reach for Kodepoint whenever you're in Kotlin Multiplatform / commonMain and need something the common standard library doesn't offer:
- "How do I check if a character is a letter/digit/uppercase in Kotlin common code?" β
Codepoint(c).isLetter(),.isDigit(),.isUpperCase()β thejava.lang.Characterequivalents, but multiplatform. - "How do I uppercase/lowercase a code point in KMP?" β
codepoint.toUpperCase()/.toLowerCase(). - "How do I iterate a string by Unicode code point without breaking emoji?" β
text.forEachCodepoint { ... }(surrogate-safe, unlikefor (c in text)). - "How do I count real characters when a string contains emoji or CJK?" β count with
forEachCodepointinstead of.length. - "How do I get a character's Unicode script or general category in common code?" β
codepoint.getUnicodeScript()/.getCategory(). - "How do I append a supplementary code point (above U+FFFF) to a
StringBuilder?" βsb.appendCodePoint(codepoint). - "How do I validate Java/Unicode identifiers in KMP?" β
.isJavaIdentifierStart(),.isUnicodeIdentifierPart(), etc.
Kodepoint covers code-point-level UCD properties. It is not an ICU replacement β for locale-aware collation, Unicode normalization (NFC/NFD), bidi, or grapheme-cluster segmentation, use a dedicated text library.
Kodepoint is published to Maven Central. Add it to your commonMain dependencies:
// build.gradle.kts
dependencies {
implementation("me.zolotov.kodepoint:kodepoint:$version")
}Replace
$versionwith the latest release shown in the badge above or on Maven Central.
import me.zolotov.kodepoint.*
// Create a code point from an Int...
val grinning = Codepoint(0x1F600) // π
// ...or from a Char.
val a = Codepoint('A'.code)
// Query Unicode properties.
a.isLetter() // true
a.isUpperCase() // true
a.toLowerCase() // Codepoint(0x61) -> "a"
a.getCategory() // Category.UPPERCASE_LETTER
grinning.getUnicodeScript() // UnicodeScript.COMMON
// Convert back to text.
grinning.asString() // "π"Walking a string with for (c in text) splits emoji and other supplementary
characters into broken surrogate halves. forEachCodepoint gives you whole
characters:
val text = "Hi ππ½ δΈη"
text.forEachCodepoint { cp ->
println("U+%04X %s".format(cp.codepoint, cp.asString()))
}
// U+0048 H
// U+0069 i
// U+0020
// U+1F44B π
// U+1F3FD π½
// U+4E16 δΈ
// U+754C η// Count code points, not UTF-16 chars ("π".length == 2).
var count = 0
"aπb".forEachCodepoint { count++ } // 3
// Append a supplementary code point to any Appendable.
val sb = StringBuilder()
sb.appendCodePoint(Codepoint(0x1F44D)) // π
sb.appendCodePoint('!'.code)
sb.toString() // "π!"A value class wrapping an Int code point.
| Member | Returns | Description |
|---|---|---|
Codepoint(Int) |
Codepoint |
Construct from a code-point value. |
Codepoint.fromChars(high, low) |
Codepoint |
Combine a high/low surrogate pair. |
codepoint |
Int |
The raw code-point value. |
charCount |
Int |
UTF-16 units needed (1 or 2). |
asString() |
String |
Encode as a String. |
Classification
| Method | Description |
|---|---|
isLetter() |
Letter (Lu, Ll, Lt, Lm, Lo). |
isDigit() |
Decimal digit (Nd). |
isLetterOrDigit() |
Letter or digit. |
isUpperCase() / isLowerCase() |
Upper- / lowercase letter. |
isSpaceChar() |
Unicode space (Zs, Zl, Zp). |
isWhitespace() |
Whitespace (see differences). |
isIdeographic() |
Ideographic character. |
isISOControl() |
Control character (Cc). |
isIdentifierIgnorable() |
Ignorable in identifiers. |
isUnicodeIdentifierStart() / isUnicodeIdentifierPart() |
Unicode identifier rules. |
isJavaIdentifierStart() / isJavaIdentifierPart() |
Java identifier rules. |
Conversion & metadata
| Method | Returns | Description |
|---|---|---|
toUpperCase() / toLowerCase() |
Codepoint |
Case mapping (unchanged if none). |
getCategory() |
Category |
General category (e.g. UPPERCASE_LETTER). |
getUnicodeScript() |
UnicodeScript |
Unicode script (e.g. LATIN, HAN). |
| Extension | Returns | Description |
|---|---|---|
forEachCodepoint { cp -> } |
Unit |
Iterate code points left-to-right (surrogate-safe, inline). |
forEachCodepointReversed { cp -> } |
Unit |
Iterate right-to-left. |
codePointAt(index) |
Codepoint |
Code point starting at index. |
codePointBefore(index) |
Codepoint |
Code point ending before index. |
codepoints(offset, direction) |
Iterator<Codepoint> |
Lazy iterator (Direction.FORWARD / BACKWARD). |
| Extension | Description |
|---|---|
appendCodePoint(Int) |
Append a code point (as its surrogate pair when needed). |
appendCodePoint(Codepoint) |
Same, taking a Codepoint. |
| Unicode version | 16.0.0 |
| Kotlin API/language version | 2.1+ |
| JVM bytecode target | 11 |
| Correctness | Non-JVM output validated against java.lang.Character for all 1,114,112 code points |
| Runtime dependencies | None |
| Platform | Targets |
|---|---|
| JVM | jvm |
| JavaScript | js, wasmJs |
| iOS | iosArm64, iosSimulatorArm64, iosX64 |
| macOS | macosArm64, macosX64 |
| tvOS | tvosArm64, tvosSimulatorArm64, tvosX64 |
| watchOS | watchosArm64, watchosSimulatorArm64, watchosX64 |
| Linux | linuxArm64, linuxX64 |
| Windows | mingwX64 |
Note: Only JVM and WasmJS targets are actively tested in CI. Other targets compile and should work correctly, but have not been thoroughly validated.
Kodepoint aims for consistent Unicode behavior across all platforms. On the JVM it delegates to java.lang.Character; elsewhere it uses generated Unicode Character Database lookup tables. Non-JVM output is validated against the JVM implementation.
These produce identical results to java.lang.Character for all 1,114,112 Unicode code points:
isLetter(),isDigit(),isLetterOrDigit()isUpperCase(),isLowerCase()toLowerCase(),toUpperCase()isSpaceChar()isIdeographic()isIdentifierIgnorable()isISOControl()isJavaIdentifierStart(),isJavaIdentifierPart()β generated directly fromjava.lang.Characterduring the build
A few functions intentionally differ from JVM behavior to follow the Unicode standard more closely.
This library uses Unicode's White_Space property, which differs from Character.isWhitespace():
| Codepoint | Character | Unicode White_Space | Java isWhitespace |
|---|---|---|---|
| U+001C | File Separator | false |
true |
| U+001D | Group Separator | false |
true |
| U+001E | Record Separator | false |
true |
| U+001F | Unit Separator | false |
true |
| U+0085 | Next Line (NEL) | true |
false |
| U+00A0 | No-Break Space | true |
false |
| U+2007 | Figure Space | true |
false |
| U+202F | Narrow No-Break Space | true |
false |
Java excludes non-breaking spaces from isWhitespace() and includes control characters that Unicode does not classify as whitespace.
JVM includes U+2E2F (VERTICAL TILDE) for backward compatibility, but this character is not in Unicode's ID_Start or ID_Continue properties. This library follows the Unicode standard.
Kodepoint is split into three modules:
lib(me.zolotov.kodepoint:kodepoint) β the public API: theCodepointvalue class plusCharSequence/Appendableextensions, with platform-specific implementations.unicodeβ generated Unicode property lookup tables used by non-JVM targets.commonβ theUnicodeScriptenum shared across modules.
For a deep dive into data storage, lookup-table layout, and platform implementations, see ARCHITECTURE.md.
Benchmark results β including history and comparisons against java.lang.Character β are published to the dashboard. Commands, categories, and the reporting pipeline are documented in benchmarks/README.md.
# Build all modules
./gradlew build
# Run tests
./gradlew allTestsIssues and pull requests are welcome. If you hit a code point that behaves differently from java.lang.Character (outside the documented differences), please open an issue.
Licensed under the Apache License 2.0.