Background

Before I started working on Javalin, I was working on the Sparkjava framework. For those who don’t know, Sparkjava is a very popular static-first DSL for creating webserver, and the getting-started snippet on the website is:

import static spark.Spark.*;

public class HelloWorld {
    public static void main(String[] args) {
        get("/hello", (req, res) -> "Hello World");
    }
}

I believe this simplicity is the main reason for why Sparkjava became so popular. At the same time, it was also one of the things that caused the most confusion and annoyance for more advanced users. These users often wanted to create more than one Spark app per JVM, or they wanted to start and stop multiple Spark apps for tests.

When I started working on Javalin, I didn’t want it to have any global static state, but I also didn’t want to lose what made Spark apps so easy to write and read. I needed to transform Sparkjava’s elegant way of building APIs from operating on a global static class, to operating on an instance.

In Sparkjava, you can declare routes in nested paths:

path("/api", () -> {
    get("/users", UserController::getAll);
    post("/users", UserController::create);
    path("/users", () -> {
        get("/:user-id", UserController::getOne);
        patch("/:user-id", UserController::update);
        delete("/:user-id", UserController::delete);
    });
});

The way the path method works is by pushing the string path onto a stack (technically a Deque), and popping it after the runnable () -> {} is finished. Everything is static, and all the methods inside are prefixed with the paths that are currently on the stack. Since everything is static, this all works out nicely.

One way to solve this without using statics would be to move everything to a Javalin instance, in which case you would end up with the following:

Javalin server = Javalin.create();
server.path("/api", () -> {
    server.get("/users", UserController::getAll);
    server.post("/users", UserController::create);
    server.path("/users", () -> {
        server.get("/:user-id", UserController::getOne);
        server.patch("/:user-id", UserController::update);
        server.delete("/:user-id", UserController::delete);
    });
});

Here the Javalin instance has a path-stack, and things are more or less okay. One downside is that the Javalin API can’t be fluent (we need the server reference), and that the server prefix is bit noisy. To make it fluent, we could provide the Javalin instance to path, but you would quickly run into a problem:

Javalin.create().path("/api", server -> {
    server.get("/users", UserController::getAll);
    server.path("/users", server2 -> { // server is taken, also, which one should we use?
        server.get("/:user-id", UserController::getOne);
    });
});

This isn’t ideal either. We end up with multiple conflicting names for the same variable. So, now what?

Static methods inside a lambda?

The way to declare a crud API in Javalin is:

import io.javalin.Javalin;
import static io.javalin.apibuilder.ApiBuilder.*;

Javalin.create(config -> {
    config.enableCorsForAllOrigins();
}).routes(() -> {
    path("users", () -> {
        get(UserController::getAll);
        post(UserController::create);
        path(":user-id", () -> {
            get(UserController::getOne);
            patch(UserController::update);
            delete(UserController::delete);
        });
    });
}).start(port);

We’re back to using static methods, but they are actually operating on an instance! These static methods only work in the scope of a Javalin#routes call, which works like this:

public Javalin routes(@NotNull EndpointGroup endpointGroup) {
    ApiBuilder.setStaticJavalin(this);
    endpointGroup.addEndpoints();
    ApiBuilder.clearStaticJavalin();
    return this;
}

Where EndpointGroup is just a fancy name for Runnable. The method sets a temporary static Javalin, adds the user-defined routes, and clears the static Javalin. The ApiBuilder has a ThreadLocal<Javalin> and a ThreadLocal<Deque<String>>. Inside the ApiBuilder, the Javalin instance is accessed through a getter which throws IllegalStateException("The static API can only be used within a routes() call."); if the ApiBuilder is called incorrectly.

That’s pretty much it for this post. We are using static methods, but they all operate on an instance. They are more like util methods (Util.get(app, route)) with a fancy syntax than global state.

This is similar to the functionality you can get from languages like Kotlin, where you could write:

val server = Javalin.create()
with(server) {
    path("users") {
        get(UserController::getAll)
            ...
        }
    }
}

I don’t think this is a pattern that is super useful in every day application logic, but I think it’s worth knowing about if you’re designing a library. I think not having to prefix your routes with server, router, etc, definitely helps with making Javalin apps more readable.

I’ve made a GitHub issue if anyone wants to discuss this post.

Addendum

It was pointed out by /u/Az4hiel that you could also write this API without scoped lambdas, but rather by using varargs:

Javalin.create(config -> {
    config.enableCorsForAllOrigins();
}).routes(
    path("users",
        get(UserController::getAll),
        post(UserController::create),
        path(":user-id",
            get(UserController::getOne),
            patch(UserController::update),
            delete(UserController::delete)
        )
    )
).start(port);

Which is actually a pretty neat approach! It makes the API much less noisy from Java, but since Javalin is also about Kotlin interop, it might be considered a downgrade compared to:

Javalin.create { config ->
    config.enableCorsForAllOrigins()
}.routes {
    path("users") {
        get(UserController::getAll)
        post(UserController::create)
        path(":user-id") {
            get(UserController::getOne)
            patch(UserController::update)
            delete(UserController::delete)
        }
    }
}.start(port)

It’s something to think about though!