Hello World [Kotlin]
Introduction
The Kotlin binding rides on the same JNA
layer as Java, so it loads the prebuilt libazul native library directly. You write
idiomatic Kotlin — a data class, a LayoutCallback SAM that returns a Dom, and the
companion-object App factory — and the generated wrappers handle the FFI.
Installation
You need Kotlin 1.9+, JDK 17+, JNA 5.14+, and the native libazul library.
Recommended: Gradle dependency
repositories {
mavenCentral()
maven { url = uri("https://azul.rs/maven") } // azul.rs-hosted artifacts
}
dependencies {
implementation("rs.azul:azul:0.2.0")
implementation("net.java.dev.jna:jna:5.14.0")
}
Manual
- Download the native library from the /releases page.
- Add the generated
Azul.ktbindings (from the examples archive underkotlin/) to your sources.
The native library must be discoverable via -Djna.library.path /
DYLD_LIBRARY_PATH / LD_LIBRARY_PATH / PATH.
Simple „Counter“ Example
package com.azul
import com.sun.jna.Pointer
// Plain data class - the "single source of truth" for app state.
class MyDataModel(var counter: Int)
private val MODEL = MyDataModel(5)
// Click callback: write the Update int through the out-pointer.
private val onClick = AzulNativeManaged.CallbackInvokerCallback { _, dataPtr, _, outPtr ->
val m = AzulHostInvoker.refanyGet(dataPtr)
val result = if (m is MyDataModel) { m.counter += 1; AzUpdate.RefreshDom.value }
else AzUpdate.DoNothing.value
outPtr!!.setInt(0, result)
}
// Typed layout callback: returns a Dom directly; the bridge splices the bytes
// into the native out-pointer internally.
private val layout = AzulHostInvoker.LayoutCallback { _, dataPtr, _ ->
val m = AzulHostInvoker.refanyGet(dataPtr)
if (m !is MyDataModel) {
Dom.createBody()
} else {
val label = Dom.createDiv()
.withCss("font-size: 32px;")
.withChild(Dom.createText(m.counter.toString()))
val buttonDom = Button.create("Increase counter")
.withButtonType(AzButtonType.Primary.value)
.onClick(m, onClick)
.dom()
Dom.createBody()
.withChild(label)
.withChild(buttonDom)
}
}
fun main() {
// `use { }` disposes the App (C-side delete) when the block exits.
App.create(AzulHostInvoker.refanyWrap(MODEL), AppConfig.create()).use { app ->
app.run(WindowCreateOptions.create(layout))
}
}
Three things to notice.
refanyWrap/refanyGetwithissmart-casts — the same object instance is handed back to every callback;if (m is MyDataModel)both guards and smart-casts. On mismatch returnDom.createBody()/AzUpdate.DoNothing.value.LayoutCallbackSAM returnsDom— the companionWindowCreateOptions.createfactory hides the host-invoker register + JNA byte-splice. Note the!!on the nullablePointer?out-pointer beforesetInt.- Fluent wrapper API —
Dom.createBody().withChild(...)andButton.create(...).withButtonType(...).onClick(data, fn).dom().AzString.toString()decodes UTF-8 intokotlin.String.
Build and run
kotlinc -J-Xmx4g -cp $JNA_JAR Azul.kt HelloWorld.kt \
-include-runtime -d hello-world.jar
# macOS requires -XstartOnFirstThread (Cocoa main-thread rule).
DYLD_LIBRARY_PATH=. java -XstartOnFirstThread -Djna.library.path=. \
-cp hello-world.jar:$JNA_JAR com.azul.HelloWorldKt
$JNA_JAR points at your jna-5.14.0.jar. On Linux/Windows drop
-XstartOnFirstThread and use LD_LIBRARY_PATH / PATH.
You should see the window pictured on the hello-world landing page.
Common errors
UnsatisfiedLinkError— native library not on the JNA library path.- No window on macOS —
-XstartOnFirstThreadmissing. - Counter does not advance — the click handler wrote
AzUpdate.DoNothing.value. NullPointerExceptiononoutPtr— the!!unwrap on the SAM's nullablePointer?arg is required; keep it.- Process hangs at exit on Windows — the example builds and runs the whole
headless layout, but the JVM may not terminate afterwards. This is a known
JNA-on-Windows behaviour: the JVM exits only once all non-daemon threads
end and the native event queue is drained, so a native (libazul) thread or
window left on the JVM thread keeps it alive. The binding itself is fine — it
passes the full run on macOS, and the Java binding (same JVM) runs on Windows
— so a fix needs a Windows-host thread dump of the hung JVM. The E2E board
reports Kotlin
⊘ SKIPon Windows for this reason.
Coming Up Next
- Application Architecture — architecting a larger Azul application
- Document Object Model — the Dom tree: node types, hierarchy, and CSS
- Hello World [Java]