Kotlin and Java
Kona is written in Kotlin, but Java applications are a first-class target. Every public API is shaped so that a Java call site reads naturally, and the Kona repository keeps a Java sample (samples/hello-java) next to the Kotlin one (samples/hello-kotlin) as part of its regression tests. When a page in these docs shows both languages, the behavior is identical; only the syntax differs.
This page explains the conventions behind that, so you can predict how any Kona API looks from your language without hunting for an example.
How Kotlin APIs appear in Java
Section titled “How Kotlin APIs appear in Java”Kotlin compiles to ordinary JVM classes. A few language features translate in predictable ways:
| Kotlin declaration | What Kotlin code writes | What Java code writes |
|---|---|---|
Property val container |
app.container |
app.getContainer() |
Mutable property var status |
response.status = 201 |
response.setStatus(201) |
object Kona with @JvmStatic members |
Kona.create(args) |
Kona.create(args) |
Nested class Application.Builder |
Application.Builder |
Application.Builder |
fun interface Handler |
trailing lambda { request -> ... } |
lambda request -> ... or method reference |
Function type (Container) -> T |
{ c -> Greeter() } |
c -> new Greeter() |
Return type T? (nullable) |
compiler forces a null check | may return null; check it yourself |
Kona marks its entry points (Kona, Env, Application.builder, Response.text/json/html, Request.get/post, Config.toBoolean) with @JvmStatic, so Java calls them as plain static methods without a .INSTANCE or .Companion hop.
Booting an application
Section titled “Booting an application”val app: Application = Kona.create(args) .providers(HttpServiceProvider(), AppServiceProvider()) .configure { config -> config.set("app.name", "Demo") } .boot()Application app = Kona.create(args) .providers(new HttpServiceProvider(), new AppServiceProvider()) .configure(config -> config.set("app.name", "Demo")) .boot();configure accepts a java.util.function.Consumer<Config>, so it takes a Java lambda directly and a Kotlin lambda through SAM conversion.
Writing a service provider
Section titled “Writing a service provider”ServiceProvider is an interface with register(Application) and boot(Application). In Kotlin, boot has an empty default body, so a Kotlin provider may leave it out. Java providers must implement both methods: the Kotlin default is not compiled as a Java default method, so leave boot empty when you have nothing to do at boot time.
class AppServiceProvider : ServiceProvider { override fun register(app: Application) { app.container.singleton(Clock::class.java) { SystemClock() } }
override fun boot(app: Application) { val router = app.resolve(Router::class.java) router.get("/time") { Response.text(app.resolve(Clock::class.java).now().toString()) } }}public final class AppServiceProvider implements ServiceProvider { @Override public void register(Application app) { app.getContainer().singleton(Clock.class, c -> new SystemClock()); }
@Override public void boot(Application app) { Router router = app.resolve(Router.class); router.get("/time", request -> Response.text(app.resolve(Clock.class).now().toString())); }}Types are always passed as Class objects: Clock::class.java in Kotlin, Clock.class in Java. There are no reified-generic shortcuts, so the same container call works identically in both languages.
Handlers
Section titled “Handlers”A handler is anything that turns a Request into a Response. Handler is a single-method interface, handle(Request): Response, so any lambda or method reference with that shape fits.
router.get("/ping") { request -> Response.text("pong") }
val users = UserController()router.get("/users") { users.index(it) }router.get("/ping", request -> Response.text("pong"));
UserController users = new UserController();router.get("/users", users::index);Middleware and next
Section titled “Middleware and next”Middleware is a function handle(request, next) that runs before a handler. Calling next passes the request further down the chain; returning without calling it ends the request early with your own response.
import org.konaframework.http.invoke // enables next(request)
val requireToken = Middleware { request, next -> if (request.header("X-Token") == "secret") next(request) else Response.json(mapOf("error" to "unauthorized"), 401)}Middleware requireToken = (request, next) -> "secret".equals(request.header("X-Token")) ? next.handle(request) : Response.json(Map.of("error", "unauthorized"), 401);In Kotlin, next(request) is an operator extension declared in org.konaframework.http; you must import org.konaframework.http.invoke for it to compile. next.handle(request) works in both languages without any import.
Route groups
Section titled “Route groups”A route group applies a URL prefix, a route-name prefix, and middleware to every route declared inside it. This is the one API with genuinely different shapes per language.
router.group(prefix = "/api", name = "api.", middleware = listOf(requireToken)) { get("/me") { Response.json(mapOf("ok" to true)) }.name("me") // GET /api/me, name "api.me"}
// Or declare several routes with a receiver block:router.routes { get("/") { Response.text("home") } post("/contact") { Response.noContent() }}router.prefix("/api").name("api.").middleware(requireToken).group(api -> { api.get("/me", request -> Response.json(Map.of("ok", true))).name("me");});
// Positional overloads, if you prefer:router.group("/api", List.of(requireToken), api -> { /* ... */ });router.group("/api", "api.", List.of(requireToken), api -> { /* ... */ });The Kotlin group(prefix =, name =, middleware =) { } form uses named arguments and a receiver lambda, so it is hidden from Java. Java gets the fluent prefix(...).name(...).middleware(...).group(Consumer<Router>) chain and Consumer overloads instead. Both build exactly the same routes.
Default arguments
Section titled “Default arguments”Kotlin parameters can have default values. Where Kona adds @JvmOverloads, Java sees one overload per optional parameter: for example Response.text(body) and Response.text(body, status), or config.get(key) and config.get(key, default). A few functions do not have Java overloads, and Java must pass every argument:
| Call | Kotlin | Java |
|---|---|---|
| Environment lookup | Env.get("KEY") |
Env.get("KEY", null), or Env.getOrDefault("KEY", "x") |
| Request input | request.input("q") |
request.input("q", null) |
| Named route URL | router.route("home") |
router.route("home", Map.of()) |
If Java reports “method cannot be applied to given types” on a Kona call, a missing default argument is the usual reason.
Generic return types
Section titled “Generic return types”Calls that look things up by string key return a generic T, inferred from the variable you assign to:
val greeter: Greeter = app.container.make("greeter")val port: Int? = app.config.get("server.port")Greeter greeter = app.getContainer().make("greeter");Integer port = app.getConfig().get("server.port");The cast is unchecked: if the stored value has another type, you get a ClassCastException where you use it. Prefer the typed config getters (getInt, getString, getBoolean) and class-based container lookups (make(Greeter.class)) when you can.
Mixing both languages in one project
Section titled “Mixing both languages in one project”A Kotlin project can contain Java classes and the other way round; Kona does not care which language a provider or controller was written in. Scaffolds use standard src/main/kotlin and src/main/java roots, so you can add the other language’s sources beside the first. grind make:controller chooses .java as soon as any .java file exists under src/main/java/; pass --lang= to override.
Reference samples
Section titled “Reference samples”samples/hello-kotlin and samples/hello-java in the Kona repository match the default web kona new source layout (Crema welcome + /api/health) with HTTP smoke tests. They are not full scaffolds: they have no ./grind or local ./gradlew, and you run them from the repository root (./gradlew :hello-kotlin:run / :hello-java:run). For the product path with ./grind serve, use Installation and Your first app. Other kits (api, brew, inertia) are covered by kona new regressions in the CLI test suite.