WHATWG URL parser for Kotlin/Native
March 24, 2026 · View on GitHub
Fast WHATWG URL Specification compliant URL parser for Kotlin/Native. Built on Ada — the same C++ URL parser used by Node.js since Node 18.
The Ada library passes the full range of tests from the specification across a wide range of platforms (Linux, macOS). It fully supports the relevant Unicode Technical Standard.
Usage
Parsing a URL
Url.parse("https://example.com/path?query#hash")?.use { url ->
println(url.href) // https://example.com/path?query#hash
println(url.protocol) // https:
println(url.host) // example.com
println(url.pathname) // /path
println(url.search) // ?query
println(url.hash) // #hash
}
Url implements AutoCloseable — always call close() or use the use {} block to free the underlying native memory.
Relative URL resolution
Url.parse("/new-path", base = "https://example.com/old")?.use { url ->
println(url.href) // https://example.com/new-path
}
Validation
Url.canParse("https://example.com") // true
Url.canParse("not a url") // false
Mutating a URL
All setters return true on success. Pass null to setPort, setHash, or setSearch to clear the component.
Url.parse("https://example.com")?.use { url ->
url.setPathname("/new-path")
url.setSearch("key=value")
url.setPort("8080")
println(url.href) // https://example.com:8080/new-path?key=value
}
Unicode and IDNA
Internationalized domain names are handled automatically during parsing and also exposed via Idna:
Idna.toAscii("meßagefactory.ca") // xn--meagefactory-m9a.ca
Idna.toUnicode("xn--meagefactory-m9a.ca") // meßagefactory.ca
URL search params
UrlSearchParams.parse("key=value&foo=bar").use { params ->
println(params.get("key")) // value
println(params.size) // 2
params.append("key", "second")
params.sort()
println(params.toString()) // foo=bar&key=value&key=second
params.keys().use { keys ->
while (keys.hasNext()) println(keys.next())
}
}
API reference
Url
| Member | Description |
|---|---|
Url.parse(input, base?) | Parses a URL string, optionally relative to a base. Returns null on failure. |
Url.canParse(input, base?) | Returns true if the input is a valid URL. |
href | The serialized URL. |
protocol | The scheme including the trailing colon (e.g. "https:"). |
host | Host and port (e.g. "example.com:8080"). |
hostname | Host without port (e.g. "example.com"). |
port | Port as a string, or "" when absent. |
pathname | The path (e.g. "/foo/bar"). |
search | The query string including ?, or "" when absent. |
hash | The fragment including #, or "" when absent. |
username | The username component. |
password | The password component. |
origin | The serialized origin (e.g. "https://example.com"). |
hostType | HostType.Domain, HostType.IPv4, or HostType.IPv6. |
schemeType | SchemeType.Https, SchemeType.Http, SchemeType.File, etc. |
components | Raw byte-offset positions of each URL component as UrlComponents. |
setHref(input) | Replaces the entire URL. |
setProtocol(input) | Sets the scheme. |
setHost(input) | Sets the host and optional port. |
setHostname(input) | Sets the host without changing the port. |
setPort(input?) | Sets the port, or clears it when null. |
setPathname(input) | Sets the path. |
setSearch(input?) | Sets the query string, or clears it when null. |
setHash(input?) | Sets the fragment, or clears it when null. |
setUsername(input) | Sets the username. |
setPassword(input) | Sets the password. |
hasCredentials() | true when username or password is non-empty. |
hasPort() | true when an explicit port is present. |
hasSearch() | true when a query string is present. |
hasHash() | true when a fragment is present. |
hasHostname() | true when a hostname is present. |
hasEmptyHostname() | true when the hostname is explicitly empty. |
hasNonEmptyUsername() | true when the username is non-empty. |
hasNonEmptyPassword() | true when the password is non-empty. |
hasPassword() | true when a password component is present. |
copy() | Returns an independent deep copy. |
toString() | Equivalent to href. |
UrlSearchParams
| Member | Description |
|---|---|
UrlSearchParams.parse(input) | Parses a query string (with or without a leading ?). |
size | Number of key/value pairs. |
isEmpty | true when there are no entries. |
append(key, value) | Adds a pair without removing existing pairs with the same key. |
set(key, value) | Sets the value for a key, removing any prior pairs with that key. |
get(key) | Returns the first value for the key, or null if not found. |
getAll(key) | Returns a UrlSearchParamsList of all values for the key. |
has(key) | true if any pair with the given key exists. |
has(key, value) | true if a pair with both the given key and value exists. |
remove(key) | Removes all pairs with the given key. |
remove(key, value) | Removes all pairs with both the given key and value. |
reset(input) | Replaces all entries by re-parsing the input string. |
sort() | Sorts pairs by key in Unicode code-point order (stable). |
keys() | Returns a UrlSearchParamsKeyIterator. |
values() | Returns a UrlSearchParamsValueIterator. |
entries() | Returns a UrlSearchParamsEntryIterator of Pair<String, String>. |
toString() | Serializes to application/x-www-form-urlencoded form. |
UrlSearchParamsList and all iterator types implement AutoCloseable — close them when done.
Idna
| Member | Description |
|---|---|
Idna.toAscii(input) | Converts a Unicode domain to its Punycode ACE form. |
Idna.toUnicode(input) | Converts a Punycode ACE domain to its Unicode form. |
Version
println(adaVersion()) // e.g. "3.4.0"
println(adaVersionComponents()) // AdaVersion(major=3, minor=4, revision=0)
Memory management
Url, UrlSearchParams, UrlSearchParamsList, and all iterator types wrap native memory and must be freed explicitly. The idiomatic pattern is use {}:
Url.parse("https://example.com")?.use { url ->
// url is freed automatically at the end of this block
}
For long-lived objects, call close() manually:
val url = Url.parse("https://example.com") ?: error("invalid URL")
try {
println(url.href)
} finally {
url.close()
}
Development
Installing dependencies
macOS
brew install gradle just llvm
llvm provides clang++. Gradle 8.13+ is required; verify with gradle --version.
Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y gradle clang just
If the packaged Gradle is older than 8.13, install a current version via sdkman:
curl -s "https://get.sdkman.io" | bash
sdk install gradle 8.13
Note (snap Gradle): If you installed Gradle via snap, Kotlin/Native's bundled
libclangmay fail to load due to a glibc version mismatch with the snap sandbox. Use the sdkman or apt version instead, or run withJAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 gradle ....
Other Linux
Install Gradle via sdkman and a C++20-capable compiler via your package manager (g++ 10+ or clang++ 10+):
curl -s "https://get.sdkman.io" | bash
sdk install gradle 8.13
All platforms
A JDK 21+ is required on all platforms:
- macOS:
brew install --cask temurin - Ubuntu:
sudo apt-get install -y openjdk-21-jdk - Any OS: sdkman —
sdk install java 21-tem
Kotlin/Native downloads its own toolchain (clang, linker, sysroots) into ~/.konan on first build automatically — no manual setup is needed beyond the C++ compiler used to compile ada.cpp.
Build
just build # or: gradle buildAdaLib
Test
just test # current platform
just test-linux # Linux (linuxX64)
just test-macos-x64 # macOS Intel
just test-macos-arm64 # macOS Apple Silicon
Lint & format
just lint # check (ktlint)
just format # auto-fix (ktlint)
All checks (matches CI)
just check
License
This code is made available under the Apache License 2.0 as well as the MIT license.