This tutorial is pretty old, please check out /tutorials/simple-frontends-with-javalin-and-vue for a more modern approach.

What You Will Learn

You will learn how to create a basic Javalin application with filters, controllers, views, authentication, localization, error handling, and more. However, this is not really a full blown tutorial, it’s more a description of a basic structure, with certain points of the code highlighted. To get the full benefit of this tutorial, please clone the example on GitHub run it, and play around.

Package structure

Package Structure

As you can see, the app is packaged by feature and not by layer. If you need to be convinced that this is a good approach, please have a look at this talk by Robert C. Martin.

This is the class that ties your app together. When you open this class, you should get an immediate understanding of how everything works:

public class Application {

    // Declare dependencies
    public static BookDao bookDao;
    public static UserDao userDao;

    public static void main(String[] args) {

        // Instantiate your dependencies
        bookDao = new BookDao();
        userDao = new UserDao();

        Javalin app = Javalin.create()
            .enableStaticFiles("/public", Location.CLASSPATH)

        app.routes(() -> {
            get(Path.Web.INDEX, IndexController.serveIndexPage);
            get(Path.Web.BOOKS, BookController.fetchAllBooks);
            get(Path.Web.ONE_BOOK, BookController.fetchOneBook);
            get(Path.Web.LOGIN, LoginController.serveLoginPage);
            post(Path.Web.LOGIN, LoginController.handleLoginPost);
            post(Path.Web.LOGOUT, LoginController.handleLogoutPost);

        app.error(404, ViewUtil.notFound);


Before-handlers, endpoints and error mapping

If your application is small, declaring before-handlers, endpoints-handlers, and after-handlers all in the same location greatly improves the readability of your code. Just by looking at the class above, you can tell that there’s a filter that strips trailing slashes from all endpoints (/books/ -> /books) and that any request can handle a locale change. You also get an overview of all the endpoints, and that there is an error-mapper for 404 errors.

Static dependencies?

This is probably not what you learned in Java class, but I believe statics are better than dependency injection when dealing with web applications / controllers. Injecting dependencies makes everything a lot more ceremonious, and as can be seen in this example you need about twice the amount of code for the same functionality. I think it complicates things without providing any real benefit, and before you say unit-testing: you’re not launching this thing into space, so you don’t need to test everything. If you want to test your controllers, then acceptance-tests are superior to mocking and unit-tests, as they test your application in the exact state it’ll be in when it’s deployed.

Path.Web and Controller.handler

It’s usually a good idea to keep your paths in some sort of constant. In the code example above I have a Path class with a subclass Web (it also has a subclass Template), which holds public final static Strings. That’s just my preference, it’s up to you how you want to do this. All my handlers are declared as static Handler fields, grouping together functionality in the same classes (based on feature). Let’s have a look at the LoginController:

public static Handler serveLoginPage = ctx -> {
    Map<String, Object> model = ViewUtil.baseModel(ctx);
    model.put("loggedOut", removeSessionAttrLoggedOut(ctx));
    model.put("loginRedirect", removeSessionAttrLoginRedirect(ctx));
    ctx.render(Path.Template.LOGIN, model);

public static Handler handleLoginPost = ctx -> {
    Map<String, Object> model = ViewUtil.baseModel(ctx);
    if (!UserController.authenticate(getQueryUsername(ctx), getQueryPassword(ctx))) {
        model.put("authenticationFailed", true);
        ctx.render(Path.Template.LOGIN, model);
    } else {
        ctx.sessionAttribute("currentUser", getQueryUsername(ctx));
        model.put("authenticationSucceeded", true);
        model.put("currentUser", getQueryUsername(ctx));
        if (getQueryLoginRedirect(req) != null) {
        ctx.render(Path.Template.LOGIN, model);

public static Handler handleLogoutPost = ctx -> {
    ctx.sessionAttribute("currentUser", null);
    ctx.sessionAttribute("loggedOut", "true");

// The origin of the request (ctx.path()) is saved in the session so
// the user can be redirected back after login
public static Handler ensureLoginBeforeViewingBooks = ctx -> {
    if (!ctx.path().startsWith("/books")) {
    if (ctx.sessionAttribute"currentUser") == null) {
        ctx.sessionAttribute("loginRedirect", ctx.path());

The above methods contain all the functionality that is related to login/logout. The serveLoginPage handler inspects the request session and puts necessary variables in the view model (did the user just log out? is there a uri to redirect the user to after login?), then renders the page. The ensureLoginBeforeViewingBooks Handler is used in a before().


Localization in Java is pretty straightforward. You create two properties files with different suffixes, for example (english) and (german), then you create a ResourceBundle:

ResourceBundle.getBundle("localization/messages", new Locale("en"));

The setup is a bit more elaborate if you clone the application (I created a small wrapper object with two methods), but the basics are very simple, and only uses native Java.

Rendering views

Javalin has native support on the Context object for rendering templates. Let’s look at the login-page again:

public static Handler serveLoginPage = ctx -> {
    Map<String, Object> model = ViewUtil.baseModel(req);
    model.put("loggedOut", removeSessionAttrLoggedOut(req));
    model.put("loginRedirect", removeSessionAttrLoginRedirect(req));
    ctx.render(Path.Template.LOGIN, model);

The template needs access to the request to check the locale and the current users, which we get from the ViewUtil.baseModel(req) method.

Example view

This code snippet shows the view for the fetchOneBook Handler, which displays one book:

        <img src="$book.largeCover" alt="$book.title">

If the book is present, display it. Else, show a localized message saying the book was not found by using the msg object that was put into the model earlier using ViewUtil.baseModel(req). The view uses a layout template @#mainLayout() which is the page frame (styles, scripts, navigation, footer, etc.).


Hopefully you’ve learned a bit about Javalin, and also Java and webapps in general. Please clone the example and suggest improvements on GitHub.