Introduction

The Javalin framework is a lightweight web framework for Java and Kotlin. It uses embedded Jetty under the covers.

You can create a Javalin instance as simply as this:

1
2
3
4
5
6
7
8
9
import io.javalin.Javalin;

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

Javalin Configuration

Javalin can be configured using a Java Consumer:

1
static Javalin create(Consumer<JavalinConfig> config)

Consumer is a functional interface and therefore allows you to provide a lambda expression as its implementation - for example:

1
2
3
Javalin.create(config -> config.showJavalinBanner = false)
        .get("/", ctx -> ctx.result("Hello World"))
        .start(7070);

If you have multiple configuration statements, you can place them inside a { ... } code block:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Javalin.create(config -> {
    config.showJavalinBanner = false;
    config.plugins.enableCors(cors -> {
        cors.add(it -> {
            // not suitable for production:
            it.anyHost();
        });
    });
})
        .get("/", ctx -> ctx.result("Hello World"))
        .start(7070);

(The above example has an additional CORS configuration lambda nested in the main config code block.)

Lambda Variables

If our block of configuration code grows large enough, it can make the Javalin creation code harder to read - especially if we also start adding more handlers to our app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Javalin.create(config -> {
    config.showJavalinBanner = false;
    config.plugins.enableCors(cors -> {
        cors.add(it -> {
            // not suitable for production:
            it.anyHost();
        });
    });
})
        .get("/hi", ctx -> ctx.result("Hello"))
        .get("/bye", ctx -> ctx.result("Goodbye"))
        // ... and maybe many more...
        .start(7070);

Lambda expressions can be assigned to variables, so we can do this:

1
2
3
4
5
6
7
8
9
private static final Consumer<JavalinConfig> CONFIG = config -> {
    config.showJavalinBanner = false;
    config.plugins.enableCors(cors -> {
        cors.add(it -> {
            // not suitable for production:
            it.anyHost();
        });
    });
};

The above code shows a variable called CONFIG with a type of Consumer<JavalinConfig> (as described eariler).

We can now use the above CONFIG variable to simplify our Javalin creation code, as follows:

1
2
3
4
5
Javalin.create(CONFIG)
        .get("/hi", ctx -> ctx.result("Hello"))
        .get("/bye", ctx -> ctx.result("Goodbye"))
        // ... and maybe many more...
        .start(7070);

The Javalin Handler Interface

The Javalin Handler interface - which is used to handle our webapp’s endpoints - is also a functional interface with one method signature:

1
abstract void handle(Context ctx)

Javalin’s Context object:

“provides you with everything you need to handle a http-request”

It is described here.

We can use lambda expressions for each of our Javalin route handlers, similarly to how we used a lambda for the config code.

Let’s assume each of our handlers needs to render a template:

1
2
3
4
5
private static final Handler HANDLE_HI = ctx -> {
    Map<String, Object> model = new HashMap<>();
    // populate the model...
    ctx.render("/hi", model);
};

and:

1
2
3
4
5
private static final Handler HANDLE_BYE = ctx -> {
    Map<String, Object> model = new HashMap<>();
    // populate the model...
    ctx.render("/bye", model);
};

Now we have the following:

1
2
3
4
Javalin.create(CONFIG)
        .get("/hi", HANDLE_HI)
        .get("/bye", HANDLE_BYE)
        .start(7070);

Using Controller Classes

We can go one step further and replace our Handler variables with methods in one or more controller classes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import io.javalin.http.Context;
import java.util.HashMap;
import java.util.Map;

public class DemoController {

    public static void handleHi(Context ctx) {
        Map<String, Object> model = new HashMap<>();
        // populate the model...
        ctx.render("/hi", model);
    }

    public static void handleBye(Context ctx) {
        Map<String, Object> model = new HashMap<>();
        // populate the model...
        ctx.render("/bye", model);
    }

}

Now we can refer to these methods as follows, using method references to replace the previous lambda expressions:

1
2
3
4
Javalin.create(CONFIG)
        .get("/hi", DemoController::handleHi)
        .get("/bye", DemoController::handleBye)
        .start(7070);

Handler Groups

Javalin also suports handler groups, including a CrudHandler interface:

1
2
3
4
5
6
7
interface CrudHandler {
    getAll(ctx)
    getOne(ctx, resourceId)
    create(ctx)
    update(ctx, resourceId)
    delete(ctx, resourceId)
}

These provide additional ways for you to organize and streamline your Javalin set-up code.

Testing Javalin Apps

There’s a very helpful starter tutorial here. A lot of the above refactorings can also help to make your tests more structured.

You can mock the Javalin Context object, as shown in the tutorial:

1
private final Context ctx = mock(Context.class);

See the tutorial for more examples.