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.
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.
Url.parse("/new-path", base = "https://example.com/old")?.use { url ->
println(url.href) // https://example.com/new-path
}Url.canParse("https://example.com") // true
Url.canParse("not a url") // falseAll 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
}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.caUrlSearchParams.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())
}
}| 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. |
| 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.
| 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. |
println(adaVersion()) // e.g. "3.4.0"
println(adaVersionComponents()) // AdaVersion(major=3, minor=4, revision=0)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()
}brew install gradle just llvmllvm provides clang++. Gradle 8.13+ is required; verify with gradle --version.
sudo apt-get update
sudo apt-get install -y gradle clang justIf 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.13Note (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 ....
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.13A 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.
just build # or: gradle buildAdaLibjust test # current platform
just test-linux # Linux (linuxX64)
just test-macos-x64 # macOS Intel
just test-macos-arm64 # macOS Apple Siliconjust lint # check (ktlint)
just format # auto-fix (ktlint)just checkThis code is made available under the Apache License 2.0 as well as the MIT license.