# Javalin 7: a Java and Kotlin web framework that is really a library

> Javalin 7.2.3 puts Jetty behind a plain-code API with no annotations and no reflection. It suits small services and teams that want to read the whole request path, and it is the wrong pick if you want a container-managed stack.

**javalin/javalin** — A simple and modern Java and Kotlin web framework

- Repository: https://github.com/javalin/javalin
- Website: https://javalin.io
- Stars: 8,355 · Forks: 649
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/javalin-javalin

## What Javalin is for, and who ends up using it

Javalin describes itself as a very lightweight web framework for Kotlin and Java that supports WebSockets, HTTP2 and async requests. The stated goals are simplicity, developer experience, and first class interoperability between the two languages. That last point shapes who picks it: a team with Kotlin services and Java services, or a codebase mid-migration between them, can use one routing API and one set of handlers instead of maintaining two idioms.

The README makes a claim that matters more than the feature list: Javalin is more of a library than a framework. It spells out what that means. You do not extend anything. There are no annotations. There is no reflection. There is no other magic, just code. For an engineer choosing a stack, that is the whole pitch. Nothing scans your classpath at startup, so startup cost and failure modes stay close to what you wrote.

The typical fit is a REST API or a small microservice, which is also what the repository topics suggest (rest-api, microservice, servlet). The typical misfit is a large application that expects the framework to own dependency injection, persistence and security as one integrated product. Javalin gives you the HTTP layer and a plugin hook, and leaves the rest to you.

## Routing, filters and mappers: the mechanism you actually write

Everything hangs off Javalin.create, which takes a config lambda. Inside it you declare routes, static file locations, concurrency settings and plugins. The app object returned by create is then started on a port. The README's hello world starts on 7070 in both languages.

Routes can be declared flat or nested through config.routes.apiBuilder, where path blocks group handlers under a prefix and a path parameter such as /{userId}. The same builder holds WebSocket endpoints through ws. Filters are registered the same way: config.routes.before runs before requests, config.routes.after runs after them, config.routes.exception handles an uncaught exception type, and config.routes.error(404) runs when the status is 404 after all other handlers. WebSocket events get their own parallel set with wsBefore, wsAfter and wsException.

JSON handling is deliberately thin. ctx.json(todos) serialises an object or array to a JSON string, and ctx.bodyAsClass<Array<Todo>>() reads a request body back into a typed value. WebSocket messages use the same idea with ctx.messageAsClass<User>() and ctx.send(user). File uploads arrive through ctx.uploadedFiles("files"), and the README's example streams each one to disk with FileUtil.streamToFile. None of this requires a mapping annotation or a generated class.

The trade-off is visible here. Because there is no reflection and no annotation processing, you get predictable behaviour and fast startup, but you also write the wiring by hand. A framework that scans for controllers removes that code; Javalin makes you keep it, and in return nothing happens at runtime that you did not write.

## Installing Javalin from Maven Central and serving a first route

Javalin is published to Maven Central as io.javalin:javalin, and the README pins 7.2.3 in both dependency snippets. Add it to your build file. For Maven:

```xml
<dependency>
    <groupId>io.javalin</groupId>
    <artifactId>javalin</artifactId>
    <version>7.2.3</version>
</dependency>
```

For Gradle, the same coordinates in Kotlin DSL:

```kotlin
implementation("io.javalin:javalin:7.2.3")
```

A minimal Java application registers one GET route and starts the server on port 7070. The README gives exactly this shape:

```java
import io.javalin.Javalin;

public class HelloWorld {
    public static void main(String[] args) {
        var app = Javalin.create(config -> {
            config.routes.get("/", ctx -> ctx.result("Hello World"));
        }).start(7070);
    }
}
```

Run it and request the root path; the handler calls ctx.result, so the response body is the string Hello World. The Kotlin version is the same call with a trailing lambda:

```kotlin
import io.javalin.Javalin

fun main() {
    val app = Javalin.create { config ->
        config.routes.get("/") { it.result("Hello World") }
    }.start(7070)
}
```

To go past hello world, the README shows a single create block that turns on virtual threads, sets an async timeout, mounts static files, enables Webjars, and nests a users API with GET, POST, PATCH and DELETE plus a WebSocket endpoint. That block is the real configuration surface, and reading it is the fastest way to understand what the framework will and will not do for you.

## Where Javalin stops helping

The library-not-framework stance is also the main limitation. Configuration lives in one create lambda, so a service with many routes, many plugins and per-environment settings has no built-in layering. You can split the lambda into methods, but Javalin itself does not offer profiles, component scanning or a configuration tree beyond what you assemble.

Because there are no annotations and no reflection, you cannot drop a controller class on the classpath and expect it to be discovered. Every route is registered by code. Teams used to Spring-style controllers will feel that as repetition, and large route surfaces will need their own conventions to stay readable.

The README's plugin example is truncated mid-statement (config.registerPlugi), so the exact registration call for a plugin in version 7 is not something this article can quote. The README does say installing a plugin is a matter of adding a dependency and registering it with Javalin, and it points to the plugin list on javalin.io. Treat the documentation site as the source for the current signature rather than copying the snippet from the README.

Finally, Javalin is an HTTP layer. Session handling, authentication schemes, database access and migrations are not part of the core described here. If your project needs those decided for you, Javalin hands the decision back.

## Javalin compared with Spring Boot and Quarkus

The most common comparison is Javalin versus Spring Boot. The difference is not size, it is where the behaviour comes from. Spring Boot builds an application context, scans for annotated components and wires them together; Javalin has no annotations and no reflection, so the routes you register are the routes that exist. Spring Boot also brings a much larger surface: data access, security, configuration profiles and an ecosystem of starters. Javalin ships the HTTP server and a plugin hook, and expects you to choose the rest.

Quarkus and Micronaut take a third position. They keep annotation-driven development but move much of the wiring to build time, which is a different answer to the same startup-cost question Javalin answers by having no wiring to do. If your reason for leaving Spring is startup time, those two are the closer comparisons. If your reason is that you want to see every line that runs, Javalin is the more direct fit.

On the server itself, Javalin runs on Jetty, which the repository topics list alongside java, kotlin and servlet. That matters for operations: you are deploying a Jetty-based application, not a WAR into a shared container, and the servlet-related modules are part of the repository layout rather than an external runtime you configure separately.

## Modules, licence and what an upgrade costs

The repository is a multi-module Maven build. Alongside the core javalin directory there are javalin-bom, javalin-bundle, javalin-micrometer, javalin-ssl, javalin-testtools and javalin-utils, plus a jacoco-coverage-report directory. That layout is the practical upgrade map: if you use metrics, TLS or test helpers, those are separate artifacts with their own versions under the same parent, so a core upgrade can require moving several coordinates at once. The recent releases are tagged as javalin-parent-7.2.3, 7.2.2 and 7.2.1, which reflects that parent-level versioning.

Javalin is licensed under Apache-2.0, and the README links to a summary of the licence at TLDR Legal. Apache-2.0 is a permissive licence with an explicit patent grant, which is generally why projects in this space choose it, but the README does not document the project's own contribution or trademark terms beyond pointing at the CONTRIBUTING guide. If your organisation has rules about attribution or notice files, read the LICENSE in the repository rather than the summary.

Maintenance is not something this article can assert from a badge. The last push to the default branch was on 2026-09-21, and the most recent release listed is 7.2.3 from 2026-08-13. Those two dates are the evidence available; the README itself makes no compatibility or support-window promise, so pin the version you test and read the release notes before moving.

## Conclusion

Adopt Javalin when you want a small service whose routing, JSON mapping and WebSocket handling live in ordinary code you can read top to bottom, and when a single Javalin.create block is an acceptable place for configuration. Skip it if you depend on annotation scanning, a servlet container's deployment model, or a framework that ships its own persistence and security layers. Before committing, verify on the documentation site how config.registerPlugin is called in your version, whether your Java runtime supports virtual threads, and which optional module (javalin-ssl, javalin-micrometer, javalin-testtools) your deployment actually needs.

## FAQ

### What is Javalin used for?

Javalin is a web framework for Kotlin and Java used to build HTTP services. The README describes it as supporting WebSockets, HTTP2 and async requests, and the repository topics list rest-api and microservice, which matches the small-service use case its examples show.

### Is Javalin similar to Spring Boot?

They overlap on HTTP handling but differ in mechanism. Javalin states there are no annotations and no reflection, so routes are registered by code, while Spring Boot wires annotated components through an application context and ships a much larger set of integrated features.

### Is Javalin still maintained?

The repository is not archived. The last push to the default branch was on 2026-09-21, and the most recent release listed is 7.2.3 from 2026-08-13.

### Is Javalin front end or backend?

It is a backend framework. It serves routes, handles request bodies and WebSocket messages, and serves static files from a configured directory, but it does not render a browser UI.

## Sources

- [javalin/javalin on GitHub](https://github.com/javalin/javalin)
- [License: Apache-2.0](https://github.com/javalin/javalin/blob/master/LICENSE)
- [Project website](https://javalin.io)
- [README](https://github.com/javalin/javalin/blob/master/README.md)
- [Releases](https://github.com/javalin/javalin/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/javalin-javalin
