An improved version of this tutorial is available for my new framework, Javalin. Show me the improved tutorial
The source code for this tutorial can be found on GitHub.

What You Will Learn

You will learn how to create a basic Spark application with filters, controllers, views, authentication, localization, error handling, and more, but 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.

Screenshot

Application Screenshot

The application is not very fancy, but offers an entity-overview, a locale-switcher, and login/logout functionality, which are all pretty essential pieces of any larger webapp.

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.

Application.java

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();

        // Configure Spark
        port(4567);
        staticFiles.location("/public");
        staticFiles.expireTime(600L);
        enableDebugScreen();

        // Set up before-filters (called before each get/post)
        before("*",                  Filters.addTrailingSlashes);
        before("*",                  Filters.handleLocaleChange);

        // Set up 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);
        get("*",                     ViewUtil.notFound);

        //Set up after-filters (called after each get/post)
        after("*",                   Filters.addGzipHeader);

    }

}

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 <a href=”https://glot.io/snippets/efivlwbva5” target=”_blank”

this example</a> you need about twice the amount of code for the same functionality. I think it complicates things wihtout 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 extact state it’ll be in when it’s deployed.

Before, routes, after

If your application is small, delcaring before-filters, routes, and after-filters 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 adds trailing slashes to all endpoints (ex: /books -> /books/) and that any page can handle a locale change. You also get an overview of all the endpoints, and see that all routes are GZIPed after everything else.

Path.Web and Controller.field

It’s usually a good idea to keep your paths in some sort of constant. In the above class 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 Route fields, grouping together functionality in the same classes (based on feature). Let’s have a look at the LoginController:


public class LoginController {

    public static Route serveLoginPage = (Request request, Response response) -> {
        Map<String, Object> model = new HashMap<>();
        model.put("loggedOut", removeSessionAttrLoggedOut(request));
        model.put("loginRedirect", removeSessionAttrLoginRedirect(request));
        return ViewUtil.render(request, model, Path.Template.LOGIN);
    };

    
    public static Route handleLoginPost = (Request request, Response response) -> {
        Map<String, Object> model = new HashMap<>();
        if (!UserController.authenticate(getQueryUsername(request), getQueryPassword(request))) {
            model.put("authenticationFailed", true);
            return ViewUtil.render(request, model, Path.Template.LOGIN);
        }
        model.put("authenticationSucceeded", true);
        request.session().attribute("currentUser", getQueryUsername(request));
        if (getQueryLoginRedirect(request) != null) {
            response.redirect(getQueryLoginRedirect(request));
        }
        return ViewUtil.render(request, model, Path.Template.LOGIN);
    };

    
    public static Route handleLogoutPost = (Request request, Response response) -> {
        request.session().removeAttribute("currentUser");
        request.session().attribute("loggedOut", true);
        response.redirect(Path.Web.LOGIN);
        return null;
    };

    
    // The origin of the request (request.pathInfo()) is saved in the session so
    // the user can be redirected back after login
    public static void ensureUserIsLoggedIn(Request request, Response response) {
        if (request.session().attribute("currentUser") == null) {
            request.session().attribute("loginRedirect", request.pathInfo());
            response.redirect(Path.Web.LOGIN);
        }
    };

}

It has four Routes/methods, serveLoginPage, handleLoginPost, handleLogoutPost, and ensureUserIsLoggedIn. This is all the functionality that is related to login/logout. The serveLoginPage Route 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 ensureUserIsLoggedIn method is used in other controllers, for example in BookController:


public static Route fetchOneBook = (Request request, Response response) -> {
    LoginController.ensureUserIsLoggedIn(request, response);
    if (clientAcceptsHtml(request)) {
        HashMap<String, Object> model = new HashMap<>();
        Book book = bookDao.getBookByIsbn(getParamIsbn(request));
        model.put("book", book);
        return ViewUtil.render(request, model, Path.Template.BOOKS_ONE);
    }
    if (clientAcceptsJson(request)) {
        return dataToJson(bookDao.getBookByIsbn(getParamIsbn(request)));
    }
    return ViewUtil.notAcceptable.handle(request, response);
};

The method intercepts the current Route fetchOneBook and redirects the user to the login page (if the user is not logged in). The origin path is stored in the sessions in ensureUserIsLoggedIn, so the user is redirected back to the correct place after login.

Response types

The fetchOneBook above controller gives three different answers based on the HTTP accept header: Try first to return HTML, then try to return JSON, finally return not-acceptable (this Route only produces HTML and JSON).

Localization

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


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

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

Rendering views

Rendering views is taken care of by another static helper, the ViewUtil:


public class ViewUtil {
   
    public static String render(Request request, Map model, String templatePath) {
        model.put("msg", new MessageBundle(getSessionLocale(request)));
        model.put("currentUser", getSessionCurrentUser(request));
        model.put("WebPath", Path.Web.class); // Access application URLs from templates
        return strictVelocityEngine().render(new ModelAndView(model, templatePath));
    }
    
}

The render method needs access to the request to check the locale and the current users. It puts this information in the template-model to ensure that the views are rendered correctly.

The ViewUtil also has some Route fields, such as notFound and notAcceptable. It’s a good place to put non-controller-specific error handling.

Example view

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


#parse("/velocity/layout.vm")
#@mainLayout()
    #if($book)
        <h1>$book.getTitle()</h1>
        <h2>$book.getAuthor()</h2>
        <img src="$book.getLargeCover()" alt="$book.getTitle()">
    #else
        <h1>$msg.get("BOOKS_BOOK_NOT_FOUND")</h1>
    #end
#end

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 viewmodel in the render() method above. The view also uses a layout template @#mainLayout() which is the page frame (styles, scripts, navigation, footer, etc.).

Conlusion

Hopefully you’ve learned a bit about Spark, and also Java and webapps in general. If you disagree with any choices made in the example-app, please create an issue on GitHub. This example will hopefully continue to evolve based on feedback and new Spark features.