Web Applications with Javalin
Web Applications with Javalin
Manish Chakravarty
Buy on Leanpub

Chapter One: Introduction to Javalin.

What is Javelin?

Javalin is a web framework runs on top of Jetty, one of the most used and stable web-servers on the JVM. You can configure the Jetty server fully, so you can easily get SSL and HTTP2 and everything else that Jetty has to offer. You can use Javelin with either Java or Kotlin languages

Javalin started as a fork of the Java and Kotlin web framework Spark, but quickly turned into a ground-up rewrite influenced by koa.js. Both of these web frameworks are inspired by the modern micro web framework grandfather: Sinatra, so if you’re coming from Ruby then Javalin shouldn’t feel too unfamiliar.

Philosophy

Like Sinatra, Javalin is not aiming to be a full web framework, but rather just a lightweight REST API library (or a micro framework, if you must). There is no concept of MVC, but there is support for template engines, WebSockets, and static file serving for convenience. This allows you to use Javalin for both creating your RESTful API backend, as well as serving an index.html with static resources (in case you’re creating an SPA). This is practical if you don’t want to deploy an apache or nginx server in addition to your Javalin service. If you wish to use Javalin to create a more traditional website instead of a REST APIs, there are several template engine wrappers available for a quick and easy setup.

API design

All the methods on the Javalin instance return this, making the API fully fluent. This will let you create a declarative and predictive REST API, that will be very easy to reason about for new developers joining your project.

Java and Kotlin interoperability

Javalin is both a Kotlin web framework and a Java web framework, meaning the API is being developed with focus on great interoperability between the two languages. The library itself is written primarily in Kotlin, but has a few core classes written in Java to achieve the best interoperability between the two languages. Javalin is intended as a “foot in the door” to Kotlin development for companies that already write a lot of Java.

When moving a Javalin project from Java to Kotlin, you shouldn’t need to learn a new way of doing things. To maintain this consistent API for both languages is an important goal of the project.

Sailient features of Javalin

Simple

Unlike other Java and Kotlin web frameworks, Javalin has very few concepts that you need to learn. You never have to extend a class and you rarely have to implement an interface.

Lightweight

Javalin is just a few thousand lines of code on top of Jetty, which means its performance is almost equivalent to pure Jetty. It also means it’s very easy to reason about the source code.

Active

A new version of Javalin has been released twice a month (on average) since the first version. Don’t worry though, every version is backwards compatible. PRs and issues are reviewed swiftly, normally every week.

Interoperable

Other Java and Kotlin web frameworks usually offer separate version for each language. Javalin is being developed with interoperability in mind, so apps are built the same way in both Java and Kotlin.

Flexible

Javalin is designed to be simple and blocking, as this is the easiest programming model to reason about. However, if you set a Future as a result, Javalin switches into asynchronous mode.

Why should I use Javelin over Spring Boot or Vert.x?

Javalin is a microservice framework with a built in app server ( making it easy to produce fat jars). Unlike Vert.x this has a simple programming model and is easier to code, debug and maintain. Spring Boot is “heavy” framework and typically requires a lot of setup for simple API app. The binaries produced are also fatter.

Javalin helps you write API based applications easily with minimal overhead and gets the job done. You also have full access to the power of JVM threading and asynchronous calls. It is, of course, possible to use all existing Java libraries, thus making Javalin an easy fit in your standard Java development ecosystem.

Can I write asynchronous code with Javalin?

Yes you can. Just set a CompletableFuture<String> or CompletableFuture<InputStream> as your result:

 1 import io.javalin.Javalin
 2 
 3 fun main(args: Array<String>) {
 4     val app = Javalin.start(7000)
 5     app.get("/") { ctx -> ctx.result(getFuture()) }
 6 }
 7 
 8 // hopefully your future is less pointless than this:
 9 private fun getFuture() = CompletableFuture<String>().app\
10 ly {
11     Executors.newSingleThreadScheduledExecutor().schedule\
12 ({ this.complete("Hello World!") }, 1, TimeUnit.SECONDS)
13 }

How “Fast” is Javelin?

This really depends on your application. Javelin apps will be “as fast” as a standard Java application

How easy is Javelin to Learn?

Javalin is a micro-framework which really does not require any additional knowledge if you are already an existing Java developer who has developed a web app before. Please note that this is not an introductory book and there are several excellent online resources and books available which teach Java and Java Web Programming basics. It is recommended to familiarize oneself with these concepts before proceeding.

Summary

Javalin is a fascinating micro-framework. I really see it as a sort of NodeJS of the JVM. Something that is needed with the abundance of opinionated and heavy-weight frameworks out there.

Chapter Two : “Hello Javelin”

Setting up your IDE

Provide instructions and a screenshot to download IntelliJ Idea.

Creating a project

IntelliJ IDEA lets you create a Maven project or add a Maven support to any existing project.

  • Launch the New Project wizard. If no project is currently opened in IntelliJ IDEA, click Create New Project on the Welcome screen: Otherwise, select File | New | Project from the main menu.
  • Select Maven from the options on the left.
  • Specify project’s SDK (JDK) or use a default one and an archetype if you want to use a predefined project template (configure your own archetype by clicking Add Archetype).
  • Click Next.
  • On the next page of the wizard, specify the following Maven basic elements that are added to the pom.xml file:
    • GroupId - a package of a new project.
    • ArtifactId - a name of your project.
    • Version - a version of a new project. By default, this field is specified automatically.
  • Click Next.
  • If you are creating a project using a Maven archetype, IntelliJ IDEA displays Maven settings that you can use to set the Maven home directory and Maven repositories. Also, you can check the archetype properties. Click Next. Specify the name and location settings. Click Finish.

Setting up Maven

You should have the following code in your pom.xml:

Example 1: pom.xml
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst\
 4 ance"
 5          xsi:schemaLocation="http://maven.apache.org/POM/\
 6 4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 7     <modelVersion>4.0.0</modelVersion>
 8     <groupId>com</groupId>
 9     <artifactId>manish</artifactId>
10     <version>1.0-SNAPSHOT</version>
11     <build>
12         <plugins>
13             <plugin>
14                 <groupId>org.apache.maven.plugins</groupI\
15 d>
16                 <artifactId>maven-compiler-plugin</artifa\
17 ctId>
18                 <configuration>
19                     <source>8</source>
20                     <target>8</target>
21                 </configuration>
22             </plugin>
23         </plugins>
24     </build>
25     <dependencies>
26         <dependency>
27             <groupId>io.javalin</groupId>
28             <artifactId>javalin</artifactId>
29             <version>1.7.0</version>
30         </dependency>
31     </dependencies>
32 </project>

TIP: To download source code of Javalin: mvn dependency:sources

A simple “Hello Javelin” app

Create a class called HelloWorld and write the following code in it:

Example 2: A Simple Hello World App
1 import io.javalin.Javalin;
2 public class HelloWorld {
3     public static void main(String[] args) {
4         Javalin app = Javalin.start(7000);
5         app.get("/", ctx -> ctx.result("Hello Javelin"));
6     }
7 }

A quick overview of the “Hello Javelin” App

Let us now go through the application describing what each component does in detail.

1 Javalin app = Javalin.start(7000);

The code above starts the application on port 7000. Let us see what internally happens when we start a Javalin app.

Javalin app internals: Startup

Example 3: Internals of Javalin startup
 1 public Javalin start() {
 2     if (!started) {
 3         if (!hideBanner) {
 4             log.info(Util.INSTANCE.javalinBanner());
 5         }
 6         Util.INSTANCE.printHelpfulMessageIfLoggerIsMissin\
 7 g();
 8         Util.INSTANCE.setNoServerHasBeenStarted(false);
 9         eventManager.fireEvent(EventType.SERVER_STARTING,\
10  this);
11         try {
12             embeddedServer = embeddedServerFactory.create\
13 (new JavalinServlet(
14                 contextPath,
15                 pathMatcher,
16                 exceptionMapper,
17                 errorMapper,
18                 jettyWsHandlers,
19                 javalinWsHandlers,
20                 logLevel,
21                 dynamicGzipEnabled,
22                 defaultContentType,
23                 defaultCharacterEncoding,
24                 maxRequestCacheBodySize
25             ), staticFileConfig);
26             log.info("Starting Javalin ...");
27             port = embeddedServer.start(port);
28             log.info("Javalin has started \\o/");
29             started = true;
30             eventManager.fireEvent(EventType.SERVER_START\
31 ED, this);
32         } catch (Exception e) {
33             log.error("Failed to start Javalin", e);
34             if (e instanceof BindException && e.getMessag\
35 e() != null) {
36                 if (e.getMessage().toLowerCase().contains\
37 ("in use")) {
38                     log.error("Port already in use. Make \
39 sure no other process is using port " + port + " and try \
40 again.");
41                 } else if (e.getMessage().toLowerCase().c\
42 ontains("permission denied")) {
43                     log.error("Port 1-1023 require elevat\
44 ed privileges (process must be started by admin).");
45                 }
46             }
47             eventManager.fireEvent(EventType.SERVER_START\
48 _FAILED, this);
49         }
50     }
51     return this;
52 }

Javalin app internals:

The second line of the Javalin app reads:

1 app.get("/", ctx -> ctx.result("Hello Javelin"));

The Context instance.

The ctx represents an instance of the Context object, which contains everything you need to handle an http request. It contains the underlying servlet-request and servlet-response, and a bunch of getters and setters. The getters operate mostly on the request-object, while the setters operate exclusively on the response object.

Context methods: A reference

  1 // REQUEST METHODS
  2 ctx.request();
  3 // get underlying HttpServletRequest
  4 ctx.anyFormParamNull("k1", "k2");
  5 // returns true if any form-param is null
  6 ctx.anyQueryParamNull("k1", "k2");
  7 // returns true if any query-param is null
  8 ctx.body();
  9 // get the request body as string
 10 ctx.bodyAsBytes();
 11 // get the request body as byte-array
 12 ctx.bodyAsClass(clazz);
 13 // convert json body to object
 14 ctx.formParam("key");
 15 // get form param
 16 ctx.formParams("key");
 17 // get form param with multiple values
 18 ctx.formParamMap();
 19 // get all form param key/values as map
 20 ctx.param("key");
 21 // get a path-parameter, ex "/:id" -> param("id")
 22 ctx.paramMap();
 23 // get all param key/values as map
 24 ctx.splat(0);
 25 // get splat by nr, ex "/*" -> splat(0)
 26 ctx.splats();
 27 // get array of splat-values
 28 ctx.attribute("key", "value");
 29 // set a request attribute
 30 ctx.attribute("key");
 31 // get a request attribute
 32 ctx.attributeMap();
 33 // get all attribute key/values as map
 34 ctx.basicAuthCredentials()
 35 // get username and password used for basic-auth
 36 ctx.contentLength();
 37 // get request content length
 38 ctx.contentType();
 39 // get request content type
 40 ctx.cookie("key");
 41 // get cookie by name
 42 ctx.cookieMap();
 43 // get all cookie key/values as map
 44 ctx.header("key");
 45 // get a header
 46 ctx.headerMap();
 47 // get all header key/values as map
 48 ctx.host();
 49 // get request host
 50 ctx.ip();
 51 // get request up
 52 ctx.isMultipart();
 53 // check if request is multipart
 54 ctx.mapFormParams("k1", "k2");
 55 // map form params to their values, returns null if any f\
 56 orm param is missing
 57 ctx.mapQueryParams("k1", "k2");
 58 // map query params to their values, returns null if any \
 59 query param is missing
 60 ctx.matchedPath();
 61 // get matched path, ex "/path/:param"
 62 ctx.next();
 63 // pass the request to the next handler
 64 ctx.path();
 65 // get request path
 66 ctx.port();
 67 // get request port
 68 ctx.protocol();
 69 // get request protocol
 70 ctx.queryParam("key");
 71 // get query param
 72 ctx.queryParams("key");
 73 // get query param with multiple values
 74 ctx.queryParamMap();
 75 // get all query param key/values as map
 76 ctx.queryString();
 77 // get request query string
 78 ctx.method();
 79 // get request method
 80 ctx.scheme();
 81 // get request scheme
 82 ctx.sessionAttribute("foo", "bar");
 83 // set session-attribute "foo" to "bar"
 84 ctx.sessionAttribute("foo");
 85 // get session-attribute "foo"
 86 ctx.sessionAttributeMap();
 87 // get all session attributes as map
 88 ctx.uploadedFile("key");
 89 // get file from multipart form
 90 ctx.uploadedFiles("key");
 91 // get files from multipart form
 92 ctx.uri();
 93 // get request uri
 94 ctx.url();
 95 // get request url
 96 ctx.userAgent();
 97 // get request user agent
 98 
 99 // RESPONSE methods
100 
101 ctx.response();
102 // get underlying HttpServletResponse
103 ctx.result("result");
104 // set result (string)
105 ctx.result(inputStream);
106 // set result (stream)
107 ctx.result(future);
108 // set result (future)
109 ctx.resultString();
110 // get response result (string)
111 ctx.resultStream();
112 // get response result (stream)
113 ctx.resultFuture();
114 // get response result (future)
115 ctx.charset("charset");
116 // set response character encoding
117 ctx.header("key", "value");
118 // set response header
119 ctx.html("body html");
120 // set result and html content type
121 ctx.json(object);
122 // set result with object-as-json
123 ctx.redirect("/location");
124 // redirect to location
125 ctx.redirect("/location", 302);
126 // redirect to location with code
127 ctx.status();
128 // get response status
129 ctx.status(404);
130 // set response status
131 ctx.cookie("key", "value");
132 // set cookie with key and value
133 ctx.cookie("key", "value", 0);
134 // set cookie with key, value, and maxage
135 ctx.cookie(cookieBuilder);
136 // set cookie using cookiebuilder
137 ctx.removeCookie("key");
138 // remove cookie by key
139 ctx.removeCookie("/path", "key");
140 // remove cookie by path and key

Server Startup and Lifecyle management

To start and stop the server, use the appropriately named start() and stop() methods.

Starting and Stopping server programmatically
1 Javalin app = Javalin.create()
2     .start() // start server (sync/blocking)
3     .stop() // stop server (sync/blocking)
Starting without any custom configuration
1 Javalin app = Javalin.start(7000);
Server setup: Available configurations
Server setup: Available configurations
 1 Javalin.create() // create has to be called first
 2     .contextPath("/context-path")
 3     // set a context path (default is "/")
 4     .dontIgnoreTrailingSlashes()
 5     // treat '/test' and '/test/' as different URLs
 6     .defaultContentType(string)
 7     // set a default content-type for responses
 8     .defaultCharacterEncoding(string)
 9     // set a default character-encoding for responses
10     .disableStartupBanner()
11     // remove the javalin startup banner from logs
12     .embeddedServer( ... )
13     // see section below
14     .enableCorsForOrigin("origin")
15     // enables cors for the specified origin(s)
16     .enableDynamicGzip()
17     // gzip response (if client accepts gzip and response\
18  is more than 1500 bytes)
19     .enableRouteOverview("/path")
20     // render a HTML page showing all mapped routes
21     .enableStandardRequestLogging()
22     // does requestLogLevel(LogLevel.STANDARD)
23     .enableStaticFiles("/public")
24     // enable static files (opt. second param Location.CL\
25 ASSPATH/Location.EXTERNAL)
26     .maxBodySizeForRequestCache(long)
27     // set max body size for request cache
28     .port(port)
29     // set the port
30     .start();
31     // start has to be called last

Custom Server

Starting a custom server
1 app.embeddedServer(new EmbeddedJettyFactory(() -> {
2     Server server = new Server();
3     // do whatever you want here
4     return server;
5 }));

Custom jetty handlers

You can configure your embedded jetty-server with a handler-chain, and Javalin will attach it’s own handlers to the end of this chain.

Custom Jetty Hander example
 1 StatisticsHandler statisticsHandler = new StatisticsHandl\
 2 er();
 3 
 4 Javalin.create()
 5     .embeddedServer(new EmbeddedJettyFactory(() -> {
 6         Server server = new Server();
 7         server.setHandler(statisticsHandler);
 8         return server;
 9     }))
10     .start();

Implementing a custom server with SSL

Implementing a custom server with SSL enabled is easy in Javalin, but not straightforward. It may require hunting around in the documentation. For your reference, here is a complete working example with SSL

A SSL Hello World example
 1 import io.javalin.Javalin;
 2 import org.eclipse.jetty.server.Connector;
 3 import org.eclipse.jetty.server.Server;
 4 import org.eclipse.jetty.server.ServerConnector;
 5 import org.eclipse.jetty.util.ssl.SslContextFactory;
 6 
 7 public class HelloWorldSecure {
 8 
 9     // This is a very basic example, a better one can be \
10 found at:
11     // https://github.com/eclipse/jetty.project/blob/jett\
12 y-9.4.x/examples/embedded/src/main/java/org/eclipse/jetty\
13 /embedded/LikeJettyXml.java#L139-L163
14     public static void main(String[] args) {
15         Javalin.create()
16             .server(() -> {
17                 Server server = new Server();
18                 ServerConnector sslConnector = new Server\
19 Connector(server, getSslContextFactory());
20                 sslConnector.setPort(443);
21                 ServerConnector connector = new ServerCon\
22 nector(server);
23                 connector.setPort(80);
24                 server.setConnectors(new Connector[]{sslC\
25 onnector, connector});
26                 return server;
27             })
28             .start()
29             .get("/", ctx -> ctx.result("Hello World")); \
30 // valid endpoint for both connectors
31     }
32 
33     private static SslContextFactory getSslContextFactory\
34 () {
35         SslContextFactory sslContextFactory = new SslCont\
36 extFactory();
37         sslContextFactory.setKeyStorePath(HelloWorldSecur\
38 e.class.getResource("/keystore.jks").toExternalForm());
39         sslContextFactory.setKeyStorePassword("password");
40         return sslContextFactory;
41     }
42 }

Implementing a custom server with HTTP/2

The following is a sample server implemented with HTTP2. There is no straight-forward example easily, please use the following code below:

HTTP/2 server with Javalin
 1 import io.javalin.Javalin;
 2 import io.javalin.embeddedserver.jetty.EmbeddedJettyFacto\
 3 ry;
 4 import org.eclipse.jetty.alpn.ALPN;
 5 import org.eclipse.jetty.alpn.server.ALPNServerConnection\
 6 Factory;
 7 import org.eclipse.jetty.http2.HTTP2Cipher;
 8 import org.eclipse.jetty.http2.server.HTTP2ServerConnecti\
 9 onFactory;
10 import org.eclipse.jetty.server.HttpConfiguration;
11 import org.eclipse.jetty.server.HttpConnectionFactory;
12 import org.eclipse.jetty.server.SecureRequestCustomizer;
13 import org.eclipse.jetty.server.Server;
14 import org.eclipse.jetty.server.ServerConnector;
15 import org.eclipse.jetty.server.SslConnectionFactory;
16 import org.eclipse.jetty.util.ssl.SslContextFactory;
17 
18 public class Main {
19 
20     public static void main(String[] args) {
21 
22         Javalin app = Javalin.create()
23             .embeddedServer(createHttp2Server())
24             .enableStaticFiles("/public")
25             .start();
26 
27         app.get("/", ctx -> ctx.result("Hello World"));
28 
29     }
30 
31     private static EmbeddedJettyFactory createHttp2Server\
32 () {
33         return new EmbeddedJettyFactory(() -> {
34             Server server = new Server();
35 
36             ServerConnector connector = new ServerConnect\
37 or(server);
38             connector.setPort(8080);
39             server.addConnector(connector);
40 
41             // HTTP Configuration
42             HttpConfiguration httpConfig = new HttpConfig\
43 uration();
44             httpConfig.setSendServerVersion(false);
45             httpConfig.setSecureScheme("https");
46             httpConfig.setSecurePort(8443);
47 
48             // SSL Context Factory for HTTPS and HTTP/2
49             SslContextFactory sslContextFactory = new Ssl\
50 ContextFactory();
51             sslContextFactory.setKeyStorePath(Main.class.\
52 getResource("/keystore.jks").toExternalForm());
53             // replace with your real keystore
54             sslContextFactory.setKeyStorePassword("passwo\
55 rd");
56             // replace with your real password
57             sslContextFactory.setCipherComparator(HTTP2Ci\
58 pher.COMPARATOR);
59             sslContextFactory.setProvider("Conscrypt");
60 
61             // HTTPS Configuration
62             HttpConfiguration httpsConfig = new HttpConfi\
63 guration(httpConfig);
64             httpsConfig.addCustomizer(new SecureRequestCu\
65 stomizer());
66 
67             // HTTP/2 Connection Factory
68             HTTP2ServerConnectionFactory h2 = new HTTP2Se\
69 rverConnectionFactory(httpsConfig);
70             ALPNServerConnectionFactory alpn = new ALPNSe\
71 rverConnectionFactory();
72             alpn.setDefaultProtocol("h2");
73 
74             // SSL Connection Factory
75             SslConnectionFactory ssl = new SslConnectionF\
76 actory(sslContextFactory, alpn.getProtocol());
77 
78             // HTTP/2 Connector
79             ServerConnector http2Connector = new ServerCo\
80 nnector(server, ssl, alpn, h2, new HttpConnectionFactory(\
81 httpsConfig));
82             http2Connector.setPort(8443);
83             server.addConnector(http2Connector);
84 
85             return server;
86         });
87     }
88 }

Note that you will have to generate a keystore locally using: keytool -genkey -alias mydomain -keyalg RSA -keystore keystore.jks -keysize 2048

Context Extensions:

Context extensions give Java developers a way of extending the Context object.

One of the most popular features of Kotlin is extension functions. When working with an object you don’t own in Java, you often end up making MyUtil.action(object, ...). If you, for example, want to serialize an object and set it as the result on the Context, you might do:

1 app.get("/", ctx -> MyMapperUtil.serialize(ctx, myMapper,\
2  myObject));

With context extensions you can add custom extensions on the context:

1 app.get("/", ctx -> ctx.use(MyMapper.class).serialize(obj\
2 ect)); // use MyMapper to serialize object

Context extensions have to be added before you can use them, this would typically be done in the first before filter of your app:

1 app.before(ctx -> ctx.register(MyMapper.class, new MyMapp\
2 er(ctx, otherDependency));

AccessManager for Authentication and Authorization

Javalin has a functional interface AccessManager, which let’s you set per-endpoint authentication and/or authorization. It’s common to use before-handlers for this, but per-endpoint security handlers give you much more explicit and readable code. You can implement your access-manager however you want, but here is an example implementation:

Access manager code snippet
 1 // Set the access-manager that Javalin should use
 2 app.accessManager((handler, ctx, permittedRoles) -> {
 3     MyRole userRole = getUserRole(ctx);
 4     if (permittedRoles.contains(userRole)) {
 5         handler.handle(ctx);
 6     } else {
 7         ctx.status(401).result("Unauthorized");
 8     }
 9 });
10 
11 Role getUserRole(Context ctx) {
12     // determine user role based on request
13     // typically done by inspecting headers
14 }
15 
16 enum MyRole implements Role {
17     ANYONE, ROLE_ONE, ROLE_TWO, ROLE_THREE;
18 }
19 
20 app.routes(() -> {
21     get("/un-secured",
22         ctx -> ctx.result("Hello"), roles(ANYONE));
23     get("/secured",
24         ctx -> ctx.result("Hello"), roles(ROLE_ONE));
25 });

Exception Mapping

All handlers (before, endpoint, after) can throw Exception (and any subclass of Exception) The app.exception() method gives you a way of handling these exceptions:

1 app.exception(NullPointerException.class, (e, ctx) -> {
2     // handle nullpointers here
3 });
4 
5 app.exception(Exception.class, (e, ctx) -> {
6     // handle general exceptions here
7     // will not trigger if more specific exception-mapper\
8  found
9 });

Javalin has a HaltException which is handled before other exceptions. It can be used to short-circuit the request-lifecycle. If you throw a HaltException in a before-handler, no endpoint-handler will fire. When throwing a HaltException you can include a status code, a message, or both:

1 throw new HaltException();                     // (status\
2 : 200, message: "Execution halted")
3 throw new HaltException(401);                  // (status\
4 : 401, message: "Execution halted")
5 throw new HaltException("My message");         // (status\
6 : 200, message: "My message")
7 throw new HaltException(401, "Unauthorized");  // (status\
8 : 401, message: "Unauthorized")
Error Mapping

Error mapping is similar to exception mapping, but it operates on HTTP status codes instead of Exceptions:

1 app.error(404, ctx -> {
2     ctx.result("Generic 404 message")
3 });

It can make sense to use them together:

1 app.exception(FileNotFoundException.class, (e, ctx) -> {
2     ctx.status(404);
3 }).error(404, ctx -> {
4     ctx.result("Generic 404 message")
5 });

WebSockets

Javalin has a very intuitive way of handling WebSockets, similar to most node frameworks:

 1 app.ws("/websocket/:path", ws -> {
 2     ws.onConnect(session -> System.out.println("Connected\
 3 "));
 4     ws.onMessage((session, message) -> {
 5         System.out.println("Received: " + message);
 6         session.getRemote().sendString("Echo: " + message\
 7 );
 8     });
 9     ws.onClose((session, statusCode, reason) -> System.ou\
10 t.println("Closed"));
11     ws.onError((session, throwable) -> System.out.println\
12 ("Errored"));
13 });

The WsSession object wraps Jetty’s Session and adds the following methods:

 1 session.send("message")
 2 // send a message to session remote (the ws client)
 3 session.queryString()
 4 // get query-string from upgrade-request
 5 session.queryParam("key")
 6 // get query-param from upgrade-request
 7 session.queryParams("key")
 8 // get query-params from upgrade-request
 9 session.queryParamMap()
10 // get query-param-map from upgrade-request
11 session.mapQueryParams("k1", "k2")
12 // map query-params to values (only useful in kotlin)
13 session.anyQueryParamNull("k1", "k2")
14 // check if any query-param from upgrade-request is null
15 session.param("key")
16 // get a path-parameter, ex "/:id" -> param("id")
17 session.paramMap()
18 // get all param key/values as map
19 session.header("key")
20 // get a header
21 session.headerMap()
22 // get all header key/values as map
23 session.host()
24 // get request host

Lifecycle events

Javalin has five lifecycle events: SERVER_STARTING, SERVER_STARTED, SERVER_START_FAILED, SERVER_STOPPING and SERVER_STOPPED. The snippet below shows all of them in action:

 1 Javalin app = Javalin.create()
 2     .event(EventType.SERVER_STARTING, e -> { ... })
 3     .event(EventType.SERVER_STARTED, e -> { ... })
 4     .event(EventType.SERVER_START_FAILED, e -> { ... })
 5     .event(EventType.SERVER_STOPPING, e -> { ... })
 6     .event(EventType.SERVER_STOPPED, e -> { ... });
 7 
 8 app.start(); // SERVER_STARTING -> (SERVER_STARTED || SER\
 9 VER_START_FAILED)
10 app.stop(); // SERVER_STOPPING -> SERVER_STOPPED

Adding a logger:

If you’re reading this, you’ve probably seen the following message while running Javalin:

1 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerB\
2 inder".
3 SLF4J: Defaulting to no-operation (NOP) logger implementa\
4 tion
5 SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBi\
6 nder for further details.
Logger dependencies for slj4j in pom.xml
1 <dependency>
2     <groupId>org.slf4j</groupId>
3     <artifactId>slf4j-simple</artifactId>
4     <version>1.7.25</version>
5 </dependency>

Asynchronous requests

Javalin 1.6.0 introduced future results. While the default threadpool (200 threads) is enough for most use cases, sometimes slow operations should be run asynchronously. Luckily it’s very easy in Javalin, just pass a CompletableFuture to ctx.result():

Example of asynchronous requests
 1 import io.javalin.Javalin
 2 
 3 fun main(args: Array<String>) {
 4     val app = Javalin.start(7000)
 5     app.get("/") { ctx -> ctx.result(getFuture()) }
 6 }
 7 
 8 // hopefully your future is less pointless than this:
 9 private fun getFuture() = CompletableFuture<String>().app\
10 ly {
11     Executors.newSingleThreadScheduledExecutor()
12      .schedule({ this.complete("Hello World!") }, 1, Time\
13 Unit.SECONDS)
14 }

You can only set future results in endpoint handlers (get/post/put/etc). After-handlers, exception-handlers and error-handlers run like you’d expect them to after the future has been resolved or rejected.

Configuring JSON Mapper and Jackson

The JSON mapper can be configured like this:

1 Gson gson = new GsonBuilder().create();
2 JavalinJsonPlugin.setJsonToObjectMapper(gson::fromJson);
3 JavalinJsonPlugin.setObjectToJsonMapper(gson::toJson);

Configuring Jackson

The JSON mapper uses Jackson by default, which can be configured by calling:

1 JavalinJacksonPlugin.configure(objectMapper)

Note that these are global settings, and can’t be configured per instance of Javalin.

Views and Templates

Javalin currently supports five template engines, as well as markdown:

 1 ctx.renderThymeleaf("/templateFile", model("firstName", "\
 2 John", "lastName", "Doe"))
 3 ctx.renderVelocity("/templateFile", model("firstName", "J\
 4 ohn", "lastName", "Doe"))
 5 ctx.renderFreemarker("/templateFile", model("firstName", \
 6 "John", "lastName", "Doe"))
 7 ctx.renderMustache("/templateFile", model("firstName", "J\
 8 ohn", "lastName", "Doe"))
 9 ctx.renderJtwig("/templateFile", model("firstName", "John\
10 ", "lastName", "Doe"))
11 ctx.renderMarkdown("/markdownFile")
12 // Javalin looks for templates/markdown files in src/reso\
13 urces

Configure:

1 JavalinThymeleafPlugin.configure(templateEngine)
2 JavalinVelocityPlugin.configure(velocityEngine)
3 JavalinFreemarkerPlugin.configure(configuration)
4 JavalinMustachePlugin.configure(mustacheFactory)
5 JavalinJtwigPlugin.configure(configuration)
6 JavalinCommonmarkPlugin.configure(htmlRenderer, markdownP\
7 arser)

Note that these are global settings, and can’t be configured per instance of Javalin.

Chapter Three: Creating a Google Docs clone with WebSockets using Javalin

What You Will Learn

In this tutorial we will create a very simple realtime collaboration tool (like google docs). We will be using WebSockets for this, as WebSockets provides us with two-way communication over a one connection, meaning we won’t have to make additional HTTP requests to send and receive messages. A WebSocket connection stays open, greatly reducing latency (and complexity). Dependencies

Create a maven project with dependencies

We will be using Javalin for our web-server and WebSockets, and slf4j for logging:

pom.xml
 1 <dependencies>
 2     <dependency>
 3         <groupId>io.javalin</groupId>
 4         <artifactId>javalin</artifactId>
 5         <version>1.7.0</version>
 6     </dependency>
 7     <dependency>
 8         <groupId>org.slf4j</groupId>
 9         <artifactId>slf4j-simple</artifactId>
10         <version>1.7.25</version>
11     </dependency>
12 </dependencies>

The Java application

The Java application is pretty straightforward. We need:

  • a data class (Collab) containing the document and the collaborators
  • a map to keep track of document-ids and Collabs
  • websocket handlers for connect/message/close

We can get the entire server done in about 40 lines:

Websockets server example
 1 import io.javalin.Javalin;
 2 import io.javalin.embeddedserver.jetty.websocket.WsSessio\
 3 n;
 4 import java.util.Map;
 5 import java.util.concurrent.ConcurrentHashMap;
 6 
 7 public class Main {
 8 
 9     private static Map<String, Collab> collabs = new Conc\
10 urrentHashMap<>();
11 
12     public static void main(String[] args) {
13 
14         Javalin.create()
15             .port(7070)
16             .enableStaticFiles("/public")
17             .ws("/docs/:doc-id", ws -> {
18                 ws.onConnect(session -> {
19                     if (getCollab(session) == null) {
20                         createCollab(session);
21                     }
22                     getCollab(session).sessions.add(sessi\
23 on);
24                     session.send(getCollab(session).doc);
25                 });
26                 ws.onMessage((session, message) -> {
27                     getCollab(session).doc = message;
28                     getCollab(session).sessions.stream().\
29 filter(WsSession::isOpen).forEach(s -> {
30                         s.send(getCollab(session).doc);
31                     });
32                 });
33                 ws.onClose((session, status, message) -> {
34                     getCollab(session).sessions.remove(se\
35 ssion);
36                 });
37             })
38             .start();
39 
40     }
41 
42     private static Collab getCollab(WsSession session) {
43         return collabs.get(session.param("doc-id"));
44     }
45 
46     private static void createCollab(WsSession session) {
47         collabs.put(session.param("doc-id"), new Collab()\
48 );
49     }
50 
51 }

We also need to create a data object for holding our document and the people working on it:

The Collboration object
 1 import io.javalin.embeddedserver.jetty.websocket.WsSessio\
 2 n;
 3 import java.util.Set;
 4 import java.util.concurrent.ConcurrentHashMap;
 5 
 6 public class Collab {
 7     public String doc;
 8     public Set<WsSession> sessions;
 9 
10     public Collab() {
11         this.doc = "";
12         this.sessions = ConcurrentHashMap.newKeySet();
13     }
14 }

Building a JavaScript Client

In order to demonstrate that our application works, we can build a JavaScript client. We’ll keep the HTML very simple, we just need a heading and a text area:

1 <body>
2     <h1>Open the URL in another tab to start collaboratin\
3 g</h1>
4     <textarea placeholder="Type something ..."></textarea>
5 </body>

The JavaScript part could also be very simple, but we want some slightly advanced features:

  • When you open the page, the app should either connect to an existing document or generate a new document with a random id
  • When a WebSocket connection is closed, it should immediately be reestablished
  • When new text is received, the user caret (“text-cursor”) should remain in the same location (easily the most complicated part of the tutorial).
 1 window.onload = setupWebSocket;
 2 window.onhashchange = setupWebSocket;
 3 
 4 if (!window.location.hash) { // document-id not present i\
 5 n url
 6     const newDocumentId = Date.now().toString(36); // thi\
 7 s should be more random
 8     window.history.pushState(null, null, "#" + newDocumen\
 9 tId);
10 }
11 
12 function setupWebSocket() {
13     const textArea = document.querySelector("textarea");
14     const ws = new WebSocket(`ws://localhost:7070/docs/${\
15 window.location.hash.substr(1)}`);
16     textArea.onkeyup = () => ws.send(textArea.value);
17     ws.onmessage = msg => {
18     // place the caret in the correct position
19         const offset = msg.data.length - textArea.value.l\
20 ength;
21         const selection = {start: textArea.selectionStart\
22 , end: textArea.selectionEnd};
23         const startsSame = msg.data.startsWith(textArea.v\
24 alue.substring(0, selection.end));
25         const endsSame = msg.data.endsWith(textArea.value\
26 .substring(selection.start));
27         textArea.value = msg.data;
28         if (startsSame && !endsSame) {
29             textArea.setSelectionRange(selection.start, s\
30 election.end);
31         } else if (!startsSame && endsSame) {
32             textArea.setSelectionRange(selection.start + \
33 offset, selection.end + offset);
34         } else { // this is what google docs does...
35             textArea.setSelectionRange(selection.start, s\
36 election.end + offset);
37         }
38     };
39     ws.onclose = setupWebSocket; // should reconnect if c\
40 onnection is closed
41 }

And that’s it! Now try opening localhost:7070 in a couple of different browser windows (that you can see simultaneously) and collaborate with yourself. Conclusion

We have a working realtime collaboration app written in less than 100 lines of Java and JavaScript. It’s very basic though, some things to add could include:

  • Show who is currently editing the document
  • Persist the data in a database at periodic intervals
  • Replace the textarea with a rich text editor, such as quill
  • Replace the textarea with a code editor such as ace for collaborative programming
  • Improving the collaborative aspects with operational transformation

The use cases are not limited to text and documents though, you should use WebSockets for any project which requires a lot of interactions with low latency. Have fun!

Chapter Four: Creating a simple chat-app with WebSockets

Aim

In this tutorial we will create a simple real-time chat application. It will feature a chat-panel that stores messages received after you join, a list of currently connected users, and an input field to send messages from. We will be using WebSockets for this, as WebSockets provides us with full-duplex communication channels over a single TCP connection, meaning we won’t have to make additional HTTP requests to send and receive messages. A WebSocket connection stays open, greatly reducing latency (and complexity). Dependencies

First, we need to create a Maven project with some dependencies: (→ Tutorial)

pom.xml
 1 <dependencies>
 2     <dependency>
 3         <groupId>io.javalin</groupId>
 4         <artifactId>javalin</artifactId>
 5         <version>1.7.0</version>
 6     </dependency>
 7     <dependency>
 8         <groupId>org.slf4j</groupId>
 9         <artifactId>slf4j-simple</artifactId>
10         <version>1.7.13</version>
11     </dependency>
12     <dependency>
13         <groupId>org.json</groupId>
14         <artifactId>json</artifactId>
15         <version>20160810</version>
16     </dependency>
17     <dependency>
18         <groupId>com.j2html</groupId>
19         <artifactId>j2html</artifactId>
20         <version>1.2.0</version>
21     </dependency>
22 </dependencies>

The Java application

The Java application is pretty straightforward. We need:

  • a map to keep track of session/username pairs.
  • a counter for number of users (nicknames are auto-incremented)
  • websocket handlers for connect/message/close
  • a method for broadcasting a message to all users
  • a method for creating the message in HTML (or JSON if you prefer)
Websockets server example
 1 public class Chat {
 2 
 3     private static Map<Session, String> userUsernameMap =\
 4  new ConcurrentHashMap<>();
 5     private static int nextUserNumber = 1; // Assign to u\
 6 sername for next connecting user
 7 
 8     public static void main(String[] args) {
 9         Javalin.create()
10             .port(7070)
11             .enableStaticFiles("/public")
12             .ws("/chat", ws -> {
13                 ws.onConnect(session -> {
14                     String username = "User" + nextUserNu\
15 mber++;
16                     userUsernameMap.put(session, username\
17 );
18                     broadcastMessage("Server", (username \
19 + " joined the chat"));
20                 });
21                 ws.onClose((session, status, message) -> {
22                     String username = userUsernameMap.get\
23 (session);
24                     userUsernameMap.remove(session);
25                     broadcastMessage("Server", (username \
26 + " left the chat"));
27                 });
28                 ws.onMessage((session, message) -> {
29                     broadcastMessage(userUsernameMap.get(\
30 session), message);
31                 });
32             })
33             .start();
34     }
35 
36     // Sends a message from one user to all users, along \
37 with a list of current usernames
38     private static void broadcastMessage(String sender, S\
39 tring message) {
40         userUsernameMap.keySet().stream().filter(Session:\
41 :isOpen).forEach(session -> {
42             try {
43                 session.getRemote().sendString(
44                     new JSONObject()
45                         .put("userMessage", createHtmlMes\
46 sageFromSender(sender, message))
47                         .put("userlist", userUsernameMap.\
48 values()).toString()
49                 );
50             } catch (Exception e) {
51                 e.printStackTrace();
52             }
53         });
54     }
55 
56     // Builds a HTML element with a sender-name, a messag\
57 e, and a timestamp
58     private static String createHtmlMessageFromSender(Str\
59 ing sender, String message) {
60         return article(
61             b(sender + " says:"),
62             span(attrs(".timestamp"), new SimpleDateForma\
63 t("HH:mm:ss").format(new Date())),
64             p(message)
65         ).render();
66     }
67 
68 }

Building a JavaScript Client

In order to demonstrate that our application works, we can build a JavaScript client. First we create our index.html:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta name="viewport" content="width=device-width, in\
 5 itial-scale=1">
 6     <title>WebsSockets</title>
 7     <link rel="stylesheet" href="style.css">
 8 </head>
 9 <body>
10     <div id="chatControls">
11         <input id="message" placeholder="Type your messag\
12 e">
13         <button id="send">Send</button>
14     </div>
15     <ul id="userlist"> <!-- Built by JS --> </ul>
16     <div id="chat">    <!-- Built by JS --> </div>
17     <script src="websocketDemo.js"></script>
18 </body>
19 </html>

As you can see, we reference a stylesheet called style.css, which can be found on GitHub.

The final step needed for completing our chat application is creating websocketDemo.js:

 1 // small helper function for selecting element by id
 2 let id = id => document.getElementById(id);
 3 
 4 //Establish the WebSocket connection and set up event han\
 5 dlers
 6 let ws = new WebSocket("ws://" + location.hostname + ":" \
 7 + location.port + "/chat");
 8 ws.onmessage = msg => updateChat(msg);
 9 ws.onclose = () => alert("WebSocket connection closed");
10 
11 // Add event listeners to button and input field
12 id("send").addEventListener("click", () => sendAndClear(i\
13 d("message").value));
14 id("message").addEventListener("keypress", function (e) {
15     if (e.keyCode === 13) { // Send message if enter is p\
16 ressed in input field
17         sendAndClear(e.target.value);
18     }
19 });
20 
21 function sendAndClear(message) {
22     if (message !== "") {
23         ws.send(message);
24         id("message").value = "";
25     }
26 }
27 
28 function updateChat(msg) { // Update chat-panel and list \
29 of connected users
30     let data = JSON.parse(msg.data);
31     id("chat").insertAdjacentHTML("afterbegin", data.user\
32 Message);
33     id("userlist").innerHTML = data.userlist.map(user => \
34 "<li>" + user + "</li>").join("");
35 }

And that’s it! Now try opening localhost:7070 in a couple of different browser windows (that you can see simultaneously) and talk to yourself.

Conclusion

Well, that was easy! We have a working real-time chat application implemented without polling, written in a total of less than 100 lines of Java and JavaScript. The implementation is very basic though, and we should at least split up the sending of the userlist and the messages (so that we don’t rebuild the user list every time anyone sends a message), but since the focus of this tutorial was supposed to be on WebSockets, I chose to do the implementation as minimal as I could be comfortable with.

Chapter Five: Working with HTML forms and a Java backend

Dependencies

First, we need to create a project with these dependencies: (→ Tutorial)

pom.xml
 1 <dependencies>
 2     <dependency>
 3         <groupId>io.javalin</groupId>
 4         <artifactId>javalin</artifactId>
 5         <version>1.7.0</version>
 6     </dependency>
 7     <dependency>
 8         <groupId>org.slf4j</groupId>
 9         <artifactId>slf4j-simple</artifactId>
10         <version>1.7.13</version>
11     </dependency>
12 </dependencies>

Setting up the backend

Create a Java file, for example Main.java, that has the following code:

Websockets server example
 1 import java.util.HashMap;
 2 import java.util.Map;
 3 
 4 import io.javalin.Javalin;
 5 
 6 public class Main {
 7 
 8     static Map<String, String> reservations = new HashMap\
 9 <String, String>() {{
10         put("saturday", "No reservation");
11         put("sunday", "No reservation");
12     }};
13 
14     public static void main(String[] args) {
15 
16         Javalin app = Javalin.create()
17             .port(7777)
18             .enableStaticFiles("/public")
19             .start();
20 
21         app.post("/make-reservation", ctx -> {
22             reservations.put(ctx.formParam("day"), ctx.fo\
23 rmParam("time"));
24             ctx.html("Your reservation has been saved");
25         });
26 
27         app.get("/check-reservation", ctx -> {
28             ctx.html(reservations.get(ctx.queryParam("day\
29 ")));
30         });
31 
32     }
33 
34 }

This will create an app which listens on port 7777, and looks for static files in your /src/resources/public folder. We have two endpoints mapped, one post, which will make a reservation, and one get, which will check your reservation. Settings up the HTML forms

Now we have to make two HTML forms for interacting with these endpoints. We can put these forms in a file /resources/public/index.html, which will be available at http://localhost:7777/. Make reservation form

 1 <h2>Make reservation:</h2>
 2 <form method="post" action="/make-reservation">
 3     Choose day
 4     <select name="day">
 5         <option value="saturday">Saturday</option>
 6         <option value="sunday">Sunday</option>
 7     </select>
 8     <br>
 9     Choose time
10     <select name="time">
11         <option value="8:00 PM">8:00 PM</option>
12         <option value="9:00 PM">9:00 PM</option>
13     </select>
14     <br>
15     <button>Submit</button>
16 </form>

To make a reservation we need to create something on the server (in this case it’s a simple map.put(), but usually you’d have a database). When creating something on the server, you should use the POST method, which can be specified by adding method=”post” to the <form> element.

In our Java code, we have a post endpoint: app.post(“/make-reservation”, ctx -> {…}. We need to tell our form to use this endpoint with the action attribute: action=”/make-reservation”. Actions are relative, so when you click submit, the browser will create a POST request to http://localhost:7777/make-reservation with the day/time values as the request-body. Check reservation form

 1 <h2>Check your reservation:</h2>
 2 <form method="get" action="/check-reservation">
 3     Choose day
 4     <select name="day">
 5         <option value="saturday">Saturday</option>
 6         <option value="sunday">Sunday</option>
 7     </select>
 8     <br>
 9     <button>Submit</button>
10 </form>

To check a reservation we need to tell the server which day we’re interested in. In this case we’re not creating anything, and our action does not change the state of the server in any way, which makes it a good candidate for a GET request.

GET requests don’t have a request-body so when you click submit the browser creates a GET request to http://localhost:7777/check-reservation?day=saturday. The values of the form are added to the URL as query-parameters. HTML form GET vs POST summary

  • POST requests should be used if the request can change the server state.
  • POST requests have their information stored in the request-body. In order to extract information from this body you have to use ctx.formParam(key) in Javalin.
  • Performing a series of GET requests should always return the same result (if no other POST request was performed in-between).
  • GET requests have no request-body, and form information is sent as query-parameters in the URL. In order to extract information from this body you have to use ctx.queryParam(key) in Javalin.

File upload example

Let’s expand our example a bit to include file uploads. We need to add a new dependency, a new endpoint, and a new form. Dependency

We need to add a dependency for handling file-uploads:

pom.xml
1 <dependency>
2     <groupId>commons-fileupload</groupId>
3     <artifactId>commons-fileupload</artifactId>
4     <version>1.3.3</version>
5 </dependency>

Endpoint

Upload example
 1 app.post("/upload-example", ctx -> {
 2     ctx.uploadedFiles("files").forEach(file -> {
 3         try {
 4             FileUtils.copyInputStreamToFile(file.getConte\
 5 nt(), new File("upload/" + file.getName()));
 6             ctx.html("Upload successful");
 7         } catch (IOException e) {
 8             ctx.html("Upload failed");
 9         }
10     });
11 });

ctx.uploadedFiles(“files”) gives us a list of files matching the name files. We then save these files to an upload folder.

HTML form

1 <h1>Upload example</h1>
2 <form method="post" action="/upload-example" enctype="mul\
3 tipart/form-data">
4     <input type="file" name="files" multiple>
5     <button>Submit</button>
6 </form>

When uploading files you need to add enctype=”multipart/form-data” to your <form>. If you want to upload multiple files, add the multiple attribute to your <input>.

Chapter Six: A comparison with Spark

<h1 class=”no-margin-top”>SparkJava and Javalin comparison</h1> People often ask about the differences between Spark and Javalin. This page shows some of them. It’s not intended to tell you which library is better, just to highlight the differences.

Handlers vs Routes and Filters

Javalin has the concept of a Handler. A Handler is void and takes a Context (which wraps HttpServletRequest and HttpServletResponse), and you operate on both the request and response through this Context.

1 app.get("/path", ctx -> ctx.result("Hello, World!"));
2 app.after("/path", ctx -> ctx.result("Actually, nevermind\
3 ..."));

Spark on the other hand has Routes and Filters. Both Route and Filter in Spark take (Request, Response) as input. Route has a return value (Object), while Filter is void.

Request and Response wrap HttpServletRequest and HttpServletResponse, respectively.

1 app.get("/path", (req, res) -> "Hello, World!");
2 app.after("/path", (req, res) -> res.body("Actually, neve\
3 rmind..."));

Some other differences:

  • Spark can run on an application server. Javalin can only run on the included embedded Jetty server.
  • Javalin supports a lot of configuration options (HTTP2, Jetty Handler chains, fully customizable Jetty Server, CORS, default values). Spark has sane defaults, but limited configuration options.
  • Spark has a static API and an instance API. Javalin only has an instance API.
  • Javalin focuses on Java/Kotlin interoperability and behaves the same in both languages. Spark has Kotlin DSL (spark-kotlin) built on top of itself with “Kotlin only” features.
  • Spark supports route mapping based on accepts/content type. Javalin doesn’t.
  • Javalin supports regex routes. Spark doesn’t.
  • Javalin has a lambda based WebSocket API which supports path-params (and other things). Spark uses an annotation based API which proxies directly to Jetty.
  • Spark has a redirect DSL redirect.get("/fromPath", "/toPath");. Javalin doesn’t.
  • Javalin has async support for long running tasks via CompletableFuture. Spark doesn’t.
  • Javalin supports context extensions (ctx.extension(MyExt.class).myMethod()). Spark doesn’t.
  • Spark has a pac4j library. Javalin doesn’t.
  • Javalin has an AccessManager interfaces with role-support. Spark doesn’t.
  • Spark is written in Java. Javalin is written in Kotlin.
  • Javalin has lifecycle events. Spark doesn’t.

Chapter 7: Javalin and Docker

What is Docker?

 1     Docker is a tool designed to make it easier to create\
 2 , deploy, and run applications by using containers.
 3     Containers allow a developer to package up an applica\
 4 tion with all of the parts it needs,
 5     such as libraries and other dependencies, and ship it\
 6  all out as one package.
 7     By doing so, thanks to the container, the developer c\
 8 an rest assured that the application will run on any othe\
 9 r
10     Linux machine regardless of any customized settings t\
11 hat machine might have that could differ from the machine
12     used for writing and testing the code.
13     In a way, Docker is a bit like a virtual machine. But\
14  unlike a virtual machine, rather than creating a whole
15     virtual operating system, Docker allows applications \
16 to use the same Linux kernel as the system that they're
17     running on and only requires applications be shipped \
18 with things not already running on the host computer.
19     This gives a significant performance boost and reduce\
20 s the size of the application.
21     &mdash; <a href="https://opensource.com/resources/wha\
22 t-docker">opensource.com</a>

Initial Setup

Before we get started, there are a few things we need to do:

Configuring Maven

This is actually where most of the work is done. In order to easily deploy a Java application anywhere, you have to create a jar file containing your application and all of its dependencies. Open the pom.xml of your Javalin Maven project and add the following configuration (below your dependencies tag):

pom.xml
 1 <distributionManagement>
 2         <repository>
 3             <id>repo</id>
 4             <name>Internal release</name>
 5             <url>your-snapshot-repo-url</url>
 6         </repository>
 7 </distributionManagement>
 8 
 9 <build>
10     <plugins>
11         <plugin>
12             <groupId>org.apache.maven.plugins</groupId>
13             <artifactId>maven-compiler-plugin</artifactId>
14             <version>2.3.2</version>
15             <configuration>
16                 <source>1.8</source>
17                 <target>1.8</target>
18             </configuration>
19         </plugin>
20         <plugin>
21             <groupId>org.apache.maven.plugins</groupId>
22             <artifactId>maven-shade-plugin</artifactId>
23             <version>3.1.0</version>
24             <executions>
25                 <!-- Run shade goal on package phase -->
26                 <execution>
27                     <phase>package</phase>
28                     <goals>
29                         <goal>shade</goal>
30                     </goals>
31                     <configuration>
32                         <shadedArtifactAttached>true</sha\
33 dedArtifactAttached>
34                         <transformers>
35                             <!-- add Main-Class to manife\
36 st file -->
37                             <transformer
38                                     implementation="org.a\
39 pache.maven.plugins.shade.resource.ManifestResourceTransf\
40 ormer">
41                                 <mainClass>HelloWorld</ma\
42 inClass>
43                             </transformer>
44                         </transformers>
45                     </configuration>
46                 </execution>
47             </executions>
48         </plugin>
49     </plugins>
50 </build>

Configuring Docker

Before we can configure anything we must create a Dockerfile. We can create a text file using any editor and name it Dockerfile. Copy below contents to the Dockerfile and move this file to root of your project.

1 FROM openjdk:8-jre-alpine
2 
3 EXPOSE 7000
4 ENTRYPOINT ["/usr/bin/java", "-jar", "/usr/share/javalin/\
5 my-javalin.jar"]
6 
7 ARG JAR_FILE
8 ADD target/${JAR_FILE} /usr/share/javalin/my-javalin.jar

When you’ve added the Dockerfile to your project, it should look like this

To build a docker image for your application we will use dockerfile-maven-plugin (dockerfile-maven-plugin). Set DOCKER_HOST environment variable as mentioned (here). In the repository section “localhost:5000” is the registry url.

pom.xml
 1 <plugin>
 2     <groupId>com.spotify</groupId>
 3     <artifactId>dockerfile-maven-plugin</artifactId>
 4     <version>1.3.6</version>
 5     <executions>
 6         <execution>
 7             <id>default</id>
 8             <goals>
 9                 <goal>build</goal>
10                 <goal>push</goal>
11             </goals>
12         </execution>
13     </executions>
14     <configuration>
15         <repository>localhost:5000/javalin</repository>
16         <tag>${project.version}</tag>
17         <buildArgs>
18             <JAR_FILE>${project.build.finalName}-shaded.j\
19 ar</JAR_FILE>
20         </buildArgs>
21     </configuration>
22 </plugin>

When you’ve added the Docker config to your pom, it should look like this

Making Javalin Listen on the Correct Port

The only thing left is making sure Javalin can handle your requests. We have exposed port 7000 in Dockerfile. That means that 7000 port on the container is accessible to the outside world. So we will configure Javalin to listen on “7000”

 1 import io.javalin.Javalin;
 2 
 3 public class HelloWorld {
 4 
 5     public static void main(String[] args) {
 6         Javalin app = Javalin.start(7000);
 7         app.get("/", ctx -> ctx.result("Hello World"));
 8     }
 9 
10 }

## Build and Push Docker image Now we can deploy our application using mvn deploy. This will build the docker image and push it to your registry server. Image name is same as repository value in the pom. Additionally we add a tag to image to specify images for different versions. So image name for this example is localhost:5000/javalin:1.0.0-SNAPSHOT. Again, make sure you’re in your project root, then enter:

1 mvn deploy

Run Docker image

Now we can run our application using docker run. open terminal then enter:

1 docker run -d -p 7000:7000 localhost:5000/javalin:1.0.0-S\
2 NAPSHOT

That’s it. Our application is now available at http://localhost:7000/

Chapter 8: Sending email with Javalin

Dependencies

First, we need to create a Maven project with some dependencies: (→ Tutorial)

pom.xml
 1 <dependencies>
 2     <dependency>
 3         <groupId>io.javalin</groupId>
 4         <artifactId>javalin</artifactId> <!-- For handlin\
 5 g http-requests -->
 6         <version>{{site.javalinversion}}</version>
 7     </dependency>
 8     <dependency>
 9         <groupId>org.apache.commons</groupId>
10         <artifactId>commons-email</artifactId> <!-- For s\
11 ending emails -->
12         <version>1.4</version>
13     </dependency>
14     <dependency>
15         <groupId>com.j2html</groupId>
16         <artifactId>j2html</artifactId> <!-- For creating\
17  HTML form -->
18         <version>1.0.0</version>
19     </dependency>
20     <dependency>
21         <groupId>org.slf4j</groupId>
22         <artifactId>slf4j-simple</artifactId> <!-- For lo\
23 gging -->
24         <version>1.7.25</version>
25     </dependency>
26 </dependencies>

Setting up the backend

We need three endpoints: GET '/', POST '/contact-us' and GET '/contact-us/success':

example
  1 import org.apache.commons.mail.*;
  2 import io.javalin.Javalin;
  3 import static j2html.TagCreator.*;
  4 
  5 public class Main {
  6 
  7     public static void main(String[] args) {
  8 
  9         Javalin app = Javalin.create()
 10             .port(7000)
 11             .start();
 12 
 13         app.get("/", ctx -> ctx.html(
 14             form().withAction("/contact-us").withMethod("\
 15 post").with(
 16                 input().withName("subject").withPlacehold\
 17 er("Subject"),
 18                 br(),
 19                 textarea().withName("message").withPlaceh\
 20 older("Your message ..."),
 21                 br(),
 22                 button("Submit")
 23             ).render()
 24         ));
 25 
 26         app.post("/contact-us", ctx -> {
 27             Email email = new SimpleEmail();
 28             email.setHostName("smtp.googlemail.com");
 29             email.setSmtpPort(465);
 30             email.setAuthenticator(new DefaultAuthenticat\
 31 or("YOUR_EMAIL", "YOUR_PASSWORD"));
 32             email.setSSLOnConnect(true);
 33             email.setFrom("YOUR_EMAIL");
 34             email.setSubject(ctx.formParam("subject")); /\
 35 / subject from HTML-form
 36             email.setMsg(ctx.formParam("message")); // me\
 37 ssage from HTML-form
 38             email.addTo("RECEIVING_ADDRESS");
 39             email.send(); // will throw email-exception i\
 40 f something is wrong
 41             ctx.redirect("/contact-us/success");
 42         });
 43 
 44         app.get("/contact-us/success", ctx -> ctx.html("Y\
 45 our message was sent"));
 46 
 47     }
 48 
 49 }
 50 ~~~~~~
 51 
 52 In order to get the above code to work, you need to make \
 53 some changes:
 54 
 55 * Change `YOUR_EMAIL` to your gmail account <small>(youre\
 56 mail@gmail.com)</small>
 57 * Change `YOUR_PASSWORD` to your gmail password*
 58 * Change `RECEIVING_ADDRESS` to where you want the email \
 59 to be sent
 60 
 61 **It might be a good idea to create a test-account instea\
 62 d of using your real gmail credentials.*
 63 
 64 When you have made the changes to the code, run the progr\
 65 am and go to `http://localhost:7000`.
 66 You will see a simple unstyled form with an input field, \
 67 a textarea and a button.
 68 Fill in the form and click the button to test your email \
 69 server. After you click the button, your browser
 70 is redirected to `/contact-us/success` (if the email was \
 71 sent).
 72 
 73 Any emails you have sent will show up in your `Sent` fold\
 74 er in the gmail web-interface.
 75 
 76 {bump-link-number}
 77 
 78 {leanpub-filename="chapter9.txt"}
 79 
 80 # Chapter 9: Deploying Javalin apps to Heroku
 81 
 82 ## What is Heroku?
 83         Heroku is a cloud application platform  a new wa\
 84 y of building and deploying web apps.
 85         Our service lets app developers spend their time \
 86 on their application code,
 87         not managing servers, deployment, ongoing operati\
 88 ons, or scaling.
 89         &mdash; <a href="https://www.heroku.com/about">he\
 90 roku.com</a>
 91 
 92 Heroku takes care of everything related to deployment, an\
 93 d gives
 94 you easy access to key commands via their tool Heroku Too\
 95 lbelt.
 96 It's very easy to get started with (as you'll soon learn)\
 97 , and it
 98 provides a nice free-tier that you can use to deploy your\
 99  webapps.
100 
101 ## Initial Setup
102 Before we get started, there are a few things we need to \
103 do:
104 
105 * Create a free Heroku account [(sign up)](https://signup\
106 .heroku.com/dc)
107 * Install [Heroku Toolbelt](https://toolbelt.heroku.com/)
108 * Install [Maven](https://maven.apache.org/guides/getting\
109 -started/maven-in-five-minutes.html)
110 * Set up the Javalin Hello World example with Maven [( T\
111 utorial)](/tutorials/maven-setup)
112 
113 ## Configuring Maven
114 This is actually where most of the work is done. In order\
115  to easily
116 deploy a Java application anywhere, you have to create a \
117 jar file
118 containing your application and all of its dependencies.
119 Open the pom.xml of your Javalin Maven project and add the
120 following configuration (below your dependencies tag):
121 
122 {title="pom.xml", lang=xml}

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>Main</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build>

Configuring Heroku

Before we can configure anything, we actually have to create a Heroku application. This can be done by using the heroku create command.
Open a terminal and navigate to your project root, then enter:

1 heroku create javalin-heroku-example #choose your own app\
2 lication name

Now that you have a Heroku application, we have to configure how to deploy it using Maven. This is pretty straightforward using the Heroku Maven plugin.

We specify the JDK version and the app-name, along with the launch config:

pom.xml
 1 <plugin>
 2     <groupId>com.heroku.sdk</groupId>
 3     <artifactId>heroku-maven-plugin</artifactId>
 4     <version>1.1.3</version>
 5     <configuration>
 6         <jdkVersion>1.8</jdkVersion>
 7         <appName>javalin-heroku-example</appName>
 8         <processTypes>
 9             <!-- Tell Heroku how to launch your applicati\
10 on -->
11             <web>java -jar ./target/javalin-heroku-exampl\
12 e-1.0-jar-with-dependencies.jar</web>
13         </processTypes>
14     </configuration>
15 </plugin>

When you’ve added the Heroku config to your pom, it should look like this

Making Javalin Listen on the Correct Port

The only thing left is making sure Javalin can handle your requests. Heroku assigns your application a new port every time you deploy it, so we have to get this port and tell Javalin to use it:

 1 import io.javalin.Javalin;
 2 
 3 public class Main {
 4 
 5     public static void main(String[] args) {
 6         Javalin app = Javalin.create()
 7             .port(getHerokuAssignedPort())
 8             .start()
 9             .get("/", ctx -> ctx.result("Hello Heroku"));
10     }
11 
12     private static int getHerokuAssignedPort() {
13         ProcessBuilder processBuilder = new ProcessBuilde\
14 r();
15         if (processBuilder.environment().get("PORT") != n\
16 ull) {
17             return Integer.parseInt(processBuilder.enviro\
18 nment().get("PORT"));
19         }
20         return 7000;
21     }
22 
23 }

Now we can deploy our application using mvn heroku:deploy.

Again, make sure you’re in your project root, then enter:

1 mvn heroku:deploy

That’s it. Our application is now available at https://<appname>.herokuapp.com/

Chapter 10: Authentication with Javalin

Dependencies

First, create a new Gradle project with the following dependencies: (→ Tutorial)

1 dependencies {
2     compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kot\
3 lin_version"
4     compile "io.javalin:javalin:{{site.javalinversion}}"
5     compile "com.fasterxml.jackson.module:jackson-module-\
6 kotlin:2.8.4"
7     compile "org.slf4j:slf4j-simple:1.7.22"
8 }

## Creating controllers We need something worth protecting. Let’s pretend we have a very important API for manipulating a user database. We make a controller-object with some dummy data and CRUD operations:

 1 import io.javalin.Context
 2 import java.util.*
 3 
 4 object UserController {
 5 
 6     private data class User(val name: String, val email: \
 7 String)
 8 
 9     private val users = hashMapOf(
10             randomId() to User(name = "Alice", email = "a\
11 lice@alice.kt"),
12             randomId() to User(name = "Bob", email = "bob\
13 @bob.kt"),
14             randomId() to User(name = "Carol", email = "c\
15 arol@carol.kt"),
16             randomId() to User(name = "Dave", email = "da\
17 ve@dave.kt")
18     )
19 
20     fun getAllUserIds(ctx: Context) {
21         ctx.json(users.keys)
22     }
23 
24     fun createUser(ctx: Context) {
25         users[randomId()] = ctx.bodyAsClass(User::class.j\
26 ava)
27     }
28 
29     fun getUser(ctx: Context) {
30         ctx.json(users[ctx.param(":user-id")!!]!!)
31     }
32 
33     fun updateUser(ctx: Context) {
34         users[ctx.param(":user-id")!!] = ctx.bodyAsClass(\
35 User::class.java)
36     }
37 
38     fun deleteUser(ctx: Context) {
39         users.remove(ctx.param(":user-id")!!)
40     }
41 
42     private fun randomId() = UUID.randomUUID().toString()
43 
44 }

We’re using !! to convert nullables to non-nullables. If :user-id is missing or users[id] returns null, we’ll get a NullPointerException and our application will crash. Handling this is outside the scope of the tutorial.

Creating roles

Now that we have our functionality, we need to define a set of roles for our system. This is done by implementing the Role interface from io.javalin.security.Role. We’ll define three roles, one for “anyone”, one for permission to read user-data, and one for permission to write user-data.

1 enum class ApiRole : Role { ANYONE, USER_READ, USER_WRITE\
2  }

Setting up the API

Now that we have roles, we can implement our endpoints:

 1 import io.javalin.ApiBuilder.*
 2 import io.javalin.Javalin
 3 import io.javalin.security.Role.roles
 4 
 5 fun main(vararg args: String) {
 6 
 7     val app = Javalin.create().apply {
 8         accessManager(Auth::accessManager)
 9     }.start()
10 
11     app.routes {
12         path("users") {
13             get(UserController::getAllUserIds, roles(ApiR\
14 ole.ANYONE))
15             post(UserController::createUser, roles(ApiRol\
16 e.USER_WRITE))
17             path(":user-id") {
18                 get(UserController::getUser, roles(ApiRol\
19 e.USER_READ))
20                 patch(UserController::updateUser, roles(A\
21 piRole.USER_WRITE))
22                 delete(UserController::deleteUser, roles(\
23 ApiRole.USER_WRITE))
24             }
25         }
26     }
27 
28 }

A role has now been given to every endpoint: * ANYONE can getAllUserIds * USER_READ can getUser * USER_WRITE can createUser, updateUser and deleteUser

Now, all that remains is to implement the access-manager (Auth::accessManager).

Implementing auth

The AccessManager interface in Javalin is pretty simple. It takes a Handler a Context and a list of Roles. The idea is that you implement code to run the handler based on what’s in the context, and what roles are set for the endpoint.

The rules for our access manager are also simple: * When endpoint has ApiRole.ANYONE, all requests will be handled * When endpoint has another role set and the request has matching credentials, the request will be handled * Else we ignore the request and send 401 Unauthorized back to the client

This translates nicely into Kotlin:

 1 fun accessManager(handler: Handler, ctx: Context, permitt\
 2 edRoles: List<Role>) {
 3     when {
 4         permittedRoles.contains(ApiRole.ANYONE) -> handle\
 5 r.handle(ctx)
 6         ctx.userRoles.any { it in permittedRoles } -> han\
 7 dler.handle(ctx)
 8         else -> ctx.status(401).json("Unauthorized")
 9     }
10 }

Extracting user-roles from the context

There is no ctx.userRoles concept in Javalin, so we need to implement it. First we need a user-table. We’ll create a map(Pair<String, String>, List<Role>) where keys are username+password in cleartext (please don’t do this for a real service), and values are user-roles:

1 private val userRoleMap = hashMapOf(
2         Pair("alice", "weak-password") to listOf(ApiRole.\
3 USER_READ),
4         Pair("bob", "better-password") to listOf(ApiRole.\
5 USER_READ, ApiRole.USER_WRITE)
6 )

Now that we have a user-table, we need to authenticate the requests. We do this by getting the username+password from the Basic-auth-header and using them as keys for the userRoleMap:

1 private val Context.userRoles: List<ApiRole>
2     get() = this.basicAuthCredentials()?.let { (username,\
3  password) ->
4         userRoleMap[Pair(username, password)] ?: listOf()
5     } ?: listOf()

When using basic auth, credentials are transferred as plain text (although base64-encoded). Remember to enable SSL if you’re using basic-auth for a real service.**

Conclusion

This tutorial showed one possible way of implementing an AccessManager in Javalin, but the interface is very flexible and you can really do whatever you want:

 1 app.accessManager(handler, ctx, permittedRoles) -> {
 2     when {
 3         ctx.host().contains("localhost") -> handler.handl\
 4 e(ctx)
 5         Math.random() > 0.5 -> handler.handle(ctx)
 6         dayOfWeek == Calendar.SUNDAY -> handler.handle(ct\
 7 x)
 8         else -> ctx.status(401).json("Unauthorized")
 9     }
10 };