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.