∞ do more, more easily

1. Introduction

Jooby is a modern, performant and easy to use web framework for Java and Kotlin built on top of your favorite web server.

Looking for previous version? The v1 documentation is still available.

Welcome!!
Java
Kotlin
import io.jooby.Jooby;

public class App extends Jooby {

  {
    get("/", ctx -> "Welcome to Jooby!");
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}

1.1. Features

1.2. Script API

Script API (a.k.a script routes) provides a fluent DSL, reflection and annotation free based on lambda functions.

We usually extends Jooby and define routes in the instance initializer:

Script with sub-class:
Java
Kotlin
import io.jooby.Jooby;

public class App extends Jooby {

  {
    get("/", ctx -> "Hello Jooby!");
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}

For Java applications we favor extending Jooby because DSL looks better. For example, no need prefix the get method with a variable.

This is not strictly necessary (of course), you may prefer to do it without extending Jooby:

Script without subclass:
Java
Kotlin
import io.jooby.Jooby;

public class App {

  public static void main(String[] args) {
    runApp(args, app -> {

      app.get("/", ctx -> "Hello Jooby!");

    });
  }
}

In case of Kotlin, it doesn’t matter which one you choose. DSL looks great with or without extending Kooby.

1.3. MVC API

The MVC API (a.k.a mvc routes) uses annotation to define routes and byte code generation to execute them.

MVC API:
Java
Kotlin
import io.jooby.annotations.*;

public class MyController {

  @GET
  public String sayHi() {
    return "Hello Jooby!";
  }
}

public class App {

  public static void main(String[] args) {
    runApp(args, app -> {

      app.use(new MyController());

    });
  }
}

More about MVC and JAX-RS support in the MVC API chapter.

1.4. Code Snippets

For simplicity and brevity we are going to skip the runApp function and extending Jooby. A code example will looks like:

Snippet
Java
Kotlin
{
  get("/", ctx -> "Snippet");
}

The use of application class or runApp function will be included when is strictly necessary.

2. Getting Started

The best way of getting started is using the jooby console. It is a small application that generates Jooby projects very quickly.

Features

  • Maven or Gradle build

  • Java or Kotlin application

  • Script or MVC routes

  • Jetty, Netty or Undertow application

  • Uber/Fat jar or Stork native launcher

  • Dockerfile

To install the console:

  • Download jooby-cli.zip

  • Unzip jooby-cli.zip in your user home directory (or any other directory you prefer to)

  • Find the native launchers in the bin directory

You might want to add the native launcher bin/jooby or bin/jooby.bat to system path variable. So its globally accessible from any location.

To simplify documentation we use jooby as command. Windows users must use jooby.bat

Setting workspace:
jooby set -w ~/Source

All code will be saved inside the ~/Source directory.

Workspace directory is ready!

Now type jooby hit ENTER.

After prompt type help create:

jooby
jooby> help create
Missing required parameter: <name>
Usage: jooby create [-dgikms] [--server=<server>] <name>
Creates a new application
      <name>              Application name or coordinates (groupId:artifactId:
                            version)
  -d, --docker            Generates a Dockerfile
  -g, --gradle            Generates a Gradle project
  -i                      Start interactive mode
  -k, --kotlin            Generates a Kotlin application
  -m, --mvc               Generates a MVC application
  -s, --stork             Add Stork Maven plugin to build (Maven only)
      --server=<server>   Choose one of the available servers: jetty, netty or
                            undertow
jooby>

The create command generates a Jooby application. Some examples:

Creates a Maven Java project:
jooby> create myapp
Creates a Maven Kotlin project:
jooby> create myapp --kotlin
Creates a Gradle Java project:
jooby> create myapp --gradle
Creates a Gradle Kotlin project:
jooby> create myapp --gradle --kotlin

Maven and Java are the default options but you can easily override those with -g -k or -gk (order doesn’t matter). Along with the build and language the create command adds two test classes: UnitTest and IntegrationTest.

Passing the -m or --mvc generates a MVC application:

Creates a Maven Java Mvc project:
jooby> create myapp --mvc

The --server option, allow you to choose between: (J)etty, (N)etty or (U)ndertow:

Creates a Maven Java Project using Undertow:
jooby> create myapp --server undertow

Maven/Gradle configuration generates an uber/fat jar at package time. Maven builds supports generation of Stork launchers.

Creates a Maven Java Project with stork launchers:
jooby> create myapp --stork

There is a -d or --docker option which generates a Dockerfile

Creates a docker file:
jooby> create myapp --docker

The default package in all these examples is set to app, to get fully control of groupId, package, version, etc…​ Use the interactive mode:

Interactive mode:
jooby> create myapp -i

3. Router

The Router is the heart of Jooby and consist of:

  • Routing algorithm (radix tree)

  • One or more routes

  • Collection of operator over routes

3.1. Route

A Route consists of three part:

Routes:
Java
Kotlin
{

  (1) (2)
  get("/foo", ctx -> {
    return "foo"; (3)
  });

  // Get example with path variable
  get("/foo/{id}", ctx -> {
    return ctx.path("id").value();
  });

  // Post example
  post("/", ctx -> {
    return ctx.body().value();
  });
}
1 HTTP method/verb, like: GET, POST, etc…​
2 Path pattern, like: /foo, /foo/{id}, etc…​
3 Handler function

The handler function always produces a result, which is send it back to the client.

3.1.1. Attributes

Attributes let you annotate a route at application bootstrap time. It functions like static metadata available at runtime:

Java
Kotlin
{
  get("/foo", ctx -> "Foo")
    .attribute("foo", "bar");
}

An attribute consist of a name and value. Values can be any object. Attributes can be accessed at runtime in a request/response cycle. For example, a security module might check for a role attribute.

Java
Kotlin
{
  decorator(next -> ctx -> {
    User user = ...;
    String role = ctx.getRoute().attribute("Role");

    if (user.hasRole(role)) {
        return next.apply(ctx);
    }

    throw new StatusCodeException(StatusCode.FORBIDDEN);
  });
}

In MVC routes you can set attributes via annotations:

Java
Kotlin
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
  String value();
}

@Path("/path")
public class AdminResource {

  @Role("admin")
  public Object doSomething() {
    ...
  }

}

{
  decorator(next -> ctx -> {
    System.out.println(ctx.getRoute().attribute("Role"));
  });
}

The previous example will print: admin. You can retrieve all the attributes of the route by calling ctx.getRoute().getAttributes().

Any runtime annotation is automatically added as route attributes following these rules: - If the annotation has a value method, then we use the annotation’s name as the attribute name. - Otherwise, we use the method name as the attribute name.

3.2. Path Pattern

3.2.1. Static

Java
Koltin
{
  get("/foo", ctx -> "Foo");
}

3.2.2. Variable

Single path variable:
Java
Kotlin
{
  (1)
  get("/user/{id}", ctx -> {
    int id = ctx.path("id").intValue(); (2)
    return id;
  });
}
1 Defines a path variable id
2 Retrieve the variable id as int
Multiple path variables:
Java
Kotlin
{
  (1)
  get("/file/{file}.{ext}", ctx -> {
    String filename = ctx.path("file").value(); (2)
    String ext = ctx.path("ext").value();   (3)
    return filename + "." + ext;
  });
}
1 Defines two path variables: file and ext
2 Retrieve string variable: file
3 Retrieve string variable: ext

3.2.3. Regex

Regex path variable:
Java
Kotlin
{
  (1)
  get("/user/{id:[0-9]+}", ctx -> {
    int id = ctx.path("id").intValue(); (2)
    return id;
  });
}
1 Defines a path variable: id. Regex expression is everything after the first :, like: [0-9]+
2 Retrieve an int value

3.2.4. * Catchall

catchall
Java
Kotlin
{
  (1)
  get("/articles/*", ctx -> {
    String catchall = ctx.path("*").value(); (2)
    return catchall;
  });

  get("/articles/*path", ctx -> {
    String path = ctx.path("path").value(); (3)
    return path;
  });
}
1 The trailing * defines a catchall pattern
2 We access to the catchall value using the * character
3 Same example, but this time we named the catchall pattern and we access to it using path variable name.

A catchall pattern must be defined at the end of the path pattern.

3.3. Handler

Application logic goes inside a handler. A handler is a function that accepts a context object and produces a result.

A context allows you to interact with the HTTP Request and manipulate the HTTP Response.

Incoming request matches exactly ONE route handler. If there is no handler, produces a 404 response.

Java
Kotlin
{
  get("/user/{id}", ctx -> ctx.path("id").value());  (1)

  get("/user/me", ctx -> "my profile");              (2)

  get("/users", ctx -> "users");                     (3)

  get("/users", ctx -> "new users");                 (4)
}

Output:

1 GET /user/ppicapiedrappicapiedra
2 GET /user/memy profile
3 Unreachable ⇒ override it by next route
4 GET /usersnew users not users

Routes with most specific path pattern (2 vs 1) has more precedence. Also, is one or more routes result in the same path pattern, like 3 and 4, last registered route hides/overrides previous route.

3.3.1. Decorator

Cross cutting concerns such as response modification, verification, security, tracing, etc. is available via Route.Decorator.

A decorator takes the next handler in the pipeline and produces a new handler:

interface Decorator {
  Handler apply(Handler next);
}
Timing decorator example:
Java
Kotlin
{
  decorator(next -> ctx -> {
    long start = System.currentTimeMillis();       (1)

    Object response = next.apply(ctx);             (2)

    long end = System.currentTimeMillis();
    long took = end - start;

    System.out.println("Took: " + took + "ms");   (3)

    return response;                              (4)
  });

  get("/", ctx -> {
    return "decorator";
  });
}
1 Saves start time
2 Proceed with execution (pipeline)
3 Compute and print latency
4 Returns a response

One or more decorator on top of a handler produces a new handler.

3.3.2. Before

The before filter runs before a handler.

A before filter takes a context as argument and don’t produces a response. It expected to operates via side effects (usually modifying the HTTP response).

interface Before {
  void apply(Context ctx);
}
Example
Java
Kotlin
{
  before(ctx -> {
    ctx.setResponseHeader("Server", "Jooby");
  });

  get("/", ctx -> {
    return "...";
  });
}

3.3.3. After

The after filter runs after a handler.

An after filter takes two arguments. The first argument is the HTTP context, while the second argument is the result/response from a functional handler or null for side-effects handler. It expected to operates via side effects, usually modifying the HTTP response (if possible) or for cleaning/trace execution.

interface After {
  void apply(Context ctx, Object result);
}
Functional Handler:
Java
Kotlin
{
  after((ctx, result) -> {
    System.out.println(result);          (1)
    ctx.setResponseHeader("foo", "bar"); (2)
  });

  get("/", ctx -> {
    return "Jooby";
  });
}
1 Prints Jooby
2 Add a response header (modifies the HTTP response)

If the target handler is a functional handler modification of HTTP response is allowed it.

For side effects handler the after filter is invoked with a null value and isn’t allowed to modify the HTTP response.

Side-Effect Handler:
Java
Kotlin
{
  after((ctx, result) -> {
    System.out.println(result);          (1)
    ctx.setResponseHeader("foo", "bar"); (2)
  });

  get("/", ctx -> {
    return ctx.send("Jooby");
  });
}
1 Prints null (no value)
2 Produces an error/exception

Exception occurs because response was already started and its impossible to alter/modify it.

Side-effects handler are all that make use of family of send methods, responseOutputStream and responseWriter.

You can check whenever you can modify the response by checking the state of isResponseStarted():

Safe After:
Java
Kotlin
{
  after((ctx, result) -> {
    if (ctx.isResponseStarted()) {
      // Don't modify response
    } else {
      // Safe to modify response
    }
  });
}

3.4. Pipeline

Route pipeline (a.k.a route stack) is a composition of one or more decorator(s) tied to a single handler:

Java
Kotlin
{
  // Increment +1
  decorator(next -> ctx -> {
    Number n = (Number) next.apply(ctx);
    return 1 + n.intValue();
  });

  // Increment +1
  decorator(next -> ctx -> {
    Number n = (Number) next.apply(ctx);
    return 1 + n.intValue();
  });

  get("/1", ctx -> 1); (1)

  get("/2", ctx -> 2); (2)
}

Output:

1 /13
2 /25

Behind the scene, Jooby builds something like:

{
  // Increment +1
  var increment = decorator(next -> ctx -> {
    Number n = (Number) next.apply(ctx);
    return 1 + n.intValue();
  });

  Handler one = ctx -> 1;

  Handler two = ctx -> 2;

  Handler handler1 = increment.then(increment).then(one);
  Handler handler2 = increment.then(increment).then(two);

  get("/1", handler1);

  get("/2", handler2);
}

Any decorator defined on top of the handler will be stacked/chained into a new handler.

Decorator without path pattern

This was a hard decision to make, but we know is the right one. Jooby 1.x uses a path pattern to define filter/decorator.

The pipeline in Jooby 1.x consists of multiple filters and handlers. They are match sequentially one by one. The following filter is always executed in Jooby 1.x

Jooby 1.x
{
   use("/*", (req, rsp, chain) -> {
     // remote call, db call
   });

   // ...
}

Suppose there is a bot trying to access and causing lot of 404 responses (path doesn’t exist). In Jooby 1.x the filter is executed for every single request sent by the bot just to realize there is NO matching route and all we need is a 404.

In Jooby 2.x this won’t happen anymore. If there is a matching handler, the pipeline will be executed. Otherwise, nothing will do!

3.4.1. Order

Order follows the what you see is what you get approach. Routes are stacked in the way they were added/defined.

Order example:
Java
Kotlin
{
  // Increment +1
  decorator(next -> ctx -> {
    Number n = (Number) next.apply(ctx);
    return 1 + n.intValue();
  });

  get("/1", ctx -> 1);                (1)

  // Increment +1
  decorator(next -> ctx -> {
    Number n = (Number) next.apply(ctx);
    return 1 + n.intValue();
  });

  get("/2", ctx -> 2);               (2)
}

Output:

1 /12
2 /24

3.4.2. Scoped Decorator

The route(Runnable) and path(String,Runnable) operators are used to group one or more routes.

A scoped decorator looks like:

Scoped decorator:
Java
Kotlin
{
  // Increment +1
  decorator(next -> ctx -> {
    Number n = (Number) next.apply(ctx);
    return 1 + n.intValue();
  });

  route(() -> {                          (1)
    // Multiply by 2
    decorator(next -> ctx -> {
      Number n = (Number) next.apply(ctx);
      return 2 * n.intValue();
    });

    get("/4", ctx -> 4);                 (2)
  });

  get("/1", ctx -> 1);                   (3)
}

Output:

1 Introduce a new scope via route operator
2 /49
3 /12

It is a normal decorator inside of one of the group operators.

3.5. Grouping routes

As showed previously, the route(Runnable) operator push a new route scope and allows you to selectively apply one or more routes.

Route operator
Java
Kotlin
{
  route(() -> {

    get("/", ctx -> "Hello");

  });
}

Route operator is for grouping one or more routes and apply cross cutting concerns to all them.

In similar fashion the path(String,Runnable) operator groups one or more routes under a common path pattern.

Routes with path prefix:
Java
Kotlin
{
   path("/api/user", () -> {    (1)

     get("/{id}", ctx -> ...);  (2)

     get("/", ctx -> ...);      (3)

     post("/", ctx -> ...);     (4)

     ...
   });
}
1 Set common prefix /api/user
2 GET /api/user/{id}
3 GET /api/user
4 POST /api/user

3.6. Composing routes

Composition is a technique for building modular applications. You can compose one or more router/application into a new one.

Composition is available through the use(Router) operator:

Composing
Java
Kotlin
public class Foo extends Jooby {
  {
    get("/foo", Context::pathString);
  }
}

public class Bar extends Jooby {
  {
    get("/bar", Context::pathString);
  }
}

public class App extends Jooby {
  {
    use(new Foo());                     (1)

    use(new Bar());                     (2)

    get("/app", Context::pathString);   (3)
  }
}
1 Imports all routes from Foo. Output: /foo/foo
2 Imports all routes from Bar. Output: /bar/bar
3 Add more routes . Output /app/app
Composing with path prefix
Java
Kotlin
public class Foo extends Jooby {
  {
    get("/foo", Context::pathString);
  }
}

public class App extends Jooby {
  {
    use("/prefix", new Foo());  (1)
  }
}
1 Now all routes from Foo will be prefixed with /prefix. Output: /prefix/foo/prefix/foo

Composition is a great option for modularization. You can easily develop/test/deploy each application indendepently and compose them all in another application.

We do provide MVC API as another alternative for modularization.

3.7. Dynamic Routing

Dynamic routing is looks similar to composition but enabled/disabled routes at runtime using a predicate.

Suppose you own two version of an API and for some time you need to support both: old and new API:

Dynamic Routing
Java
Kotlin
public class V1 extends Jooby {
  {
    get("/api", ctx -> "v1");
  }
}

public class V2 extends Jooby {
  {
    get("/api", ctx -> "v2");
  }
}

public class App extends Jooby {
  {
    use(ctx -> ctx.header("version").value().equals("v1"), new V1()); (1)

    use(ctx -> ctx.header("version").value().equals("v2"), new V2()); (2)
  }
}

Output:

1 /apiv1; when version header is v1
2 /apiv2; when version header is v2

Done ♡!

4. Context

A Context allows you to interact with the HTTP Request and manipulate the HTTP Response

4.1. Parameters

There are several parameter types: header, cookie, path, query, form and multipart. All them share an unified/type-safe API for accessing and manipulating their values.

We are going to describe them briefly in the next sections, then go into specific features of the Value API.

4.1.1. Header

HTTP headers allow the client and the server to pass additional information with the request or the response.

Java
Kotlin
{
  get("/", ctx -> {
    String token = ctx.header("token").value();      (1)

    Value headers = ctx.headers();                   (2)

    Map<String, String> headerMap = ctx.headerMap(); (3)
    ...
  });

}
1 Header variable token
2 All headers as Value
3 All headers as map

Request cookies are send to the server using the Cookie header, but we do provide a simple key/value access to them:

Cookies
Java
Kotlin
{
  get("/", ctx -> {
    String token = ctx.cookie("token").value();      (1)

    Map<String, String> cookieMap = ctx.cookieMap(); (2)
    ...
  });

}
1 Cookie variable token
2 All cookies as map

4.1.3. Path

Path parameter are part of the URI. To define a path variable you need to use the {identifier} notation.

Syntax:
Java
Kotlin
{
  get("/{id}" ctx -> ctx.path("id").value());                                 (1)

  get("/@{id}" ctx -> ctx.path("id").value());                                (2)

  get("/file/{name}.{ext}", ctx -> cxt.path("name") + "." + ctx.path("ext")); (3)

  get("/file/*", ctx -> ctx.path("*"))                                        (4)

  get("/{id:[0-9]+}", ctx -> ctx.path("id))                                   (5)
}
1 Path variable id
2 Path variable id prefixed with @
3 Multiple variables name and ext
4 Unnamed catchall path variable
5 Path variable with a regular expression
Java
Kotlin
{
  get("/{name}", ctx -> {
    String pathString = ctx.pathString();         (1)

    Value path = ctx.path();                      (2)

    Map<String, String> pathMap = ctx.pathMap();  (3)

    String name = ctx.path("name").value();       (4)

    ...
  });
}
1 Access to the raw path string:
  • /a+b/a+b

  • /a b/a%20b (not decoded)

  • /%2F%2B/%2F%2B (not decoded)

2 Path as Value object:
  • /a+b{name=a+b}

  • /a b{name=a b} (decoded)

  • /%2F%2B{name=/+} (decoded)

3 Path as Map<String, String> object:
  • /a+b{name=a+b}

  • /a b{name=a b} (decoded)

  • /%2F%2B{name=/+} (decoded)

4 Path variable name as String:
  • /a+ba+b

  • /a ba b (decoded)

  • /%2F%2B/+ (decoded)

4.1.4. Query

Query String is part of the URI that start after the ? character.

Java
Kotlin
{
  get("/search", ctx -> {
    String queryString = ctx.queryString();                    (1)

    QueryString query = ctx.query();                           (2)

    Map<String, List<String>> queryMap = ctx.queryMultimap();  (3)

    String q = ctx.query("q").value();                         (4)

    SearchQuery searchQuery = ctx.query(SearchQuery.class);    (5)

    ...
  });
}

class SearchQuery {

   public final String q;

   public SearchQuery(String q) {
     this.q = q;
   }
}
1 Access to raw queryString:
  • /search"" (empty)

  • /search?q=a+b?q=a+b

  • /search?q=a b?q=a%20b (not decoded)

2 Query String as QueryString object:
  • /search{} (empty)

  • /search?q=a+b{q=a+b}

  • /search?q=a b{q=a b} (decoded)

3 Query string as multi-value map
  • /search{} (empty)

  • /search?q=a+b{q=[a+b]}

  • /search?q=a b{q=[a b]} (decoded)

4 Access to decoded variable q:
  • /searchBad Request (400). Missing value: "q"

  • /search?q=a+ba+b

  • /search?q=a ba b (decoded)

5 Query string as SearchQuery
  • /searchBad Request (400). Missing value: "q"

  • /search?q=a+bSearchQuery(q="a+b")

  • /search?q=a bSearchQuery(q="a b") (decoded)

4.1.5. Formdata

Formdata is expected to be in HTTP body, or for as part of the URI for GET requests.

Data is expected to be encoded as application/x-www-form-urlencoded.

Java
Kotlin
{
  post("/user", ctx -> {
    Formdata form = ctx.form();                             (1)

    Map<String, List<String>> formMap = ctx.formMultimap(); (2)

    String userId = ctx.form("id").value();                 (3)

    String pass = ctx.form("pass").value();                 (4)

    User user = ctx.form(User.class);                       (5)

    ...
  });
}

class User {

   public final String id;

   public final String pass;

   public User(String id, String pass) {
     this.id = id;
     this.pass = pass;
   }
}
curl -d "id=root&pass=pwd" -X POST http://localhost:8080/user
1 Form as Formdata{id=root, pass=pwd}
2 Form as multi-value map{id=root, pass=[pwd]}
3 Form variable idroot
4 Form variable passpwd
5 Form as User object ⇒ User(id=root, pass=pwd)

4.1.6. Multipart

Form-data must be present in the HTTP body and encoded as multipart/form-data:

Java
Kotlin
{
  post("/user", ctx -> {
    Multipart multipart = ctx.multipart();                            (1)

    Map<String, List<String> multipartMap = ctx.multipartMultimap();  (2)

    String userId = ctx.multipart("id").value();                      (3)

    String pass = ctx.multipart("pass").value();                      (4)

    FileUpload pic = ctx.multipart("pic").fileUpload();               (5)

    User user = ctx.multipart(User.class);                            (6)

    ...
  });
}

class User {

   public final String id;

   public final String pass;

   public final FileUpload pic;

   public User(String id, String pass, FileUpload pic) {
     this.id = id;
     this.pass = pass;
     this.pic = pic;
   }
}
curl -F id=root -F pass=root -F pic=@/path/to/local/file/profile.png http://localhost:8080/user
1 Form as Multipart{id=root, pass=pwd, pic=profile.png}
2 Form as multi-value map{id=root, pass=[pwd]}
3 Form variable idroot
4 Form variable passpwd
5 FileUpload variable pic
6 Form as User object ⇒ User(id=root, pass=pwd, pic=profile.png)
File Upload

You may prefer to use one of the utility methods:

Java
Kotlin
  FileUpload pic = ctx.file("pic");         (1)

  List<FileUpload> pic = ctx.files("pic");  (2)

  List<FileUpload> files = ctx.files();     (3)
1 Single file upload named pic
2 Multiple file uploads named pic
3 All file uploads

4.1.7. Flash

Flash parameters are designed to transport success/error messages between requests. It is similar to a Session but the lifecycle is shorter: data is kept for only one request.

Java
Kotlin
  get("/", ctx -> {
    return ctx.flash("success").value("Welcome!"); (3)
  });

  post("/save", ctx -> {
    ctx.flash("success", "Item created");          (1)
    return ctx.sendRedirect("/");                  (2)
  });
1 Set a flash attribute: success
2 Redirect to home page
3 Display an existing flash attribute success or shows Welcome!

Flash attributes are implemented using an HTTP Cookie. To customize the cookie name (defaults to jooby.flash) use the javadoc:FlashScope extension:

Java
Kotlin
  install(new FlashScope(new Cookie("myflash").setHttpOnly(true)));

4.2. Value API

The Value is an unified and type-safe API across all parameter types:

For learning purpose we are going to show all the Value features using query parameters, but keep in mind these features apply to all the parameter types.

4.2.1. Single value

Single value is available via value() or [type]Value() functions:

Java
Kotlin
{
  get("/", ctx -> {
    String name = ctx.query("name").value();                          (1)

    float score = ctx.query("score").floatValue();                    (2)

    boolean enabled = ctx.query("enabled").booleanValue();            (3)

    BigDecimal decimal = ctx.query("decimal").value(BigDecimal::new); (4)
    ...
  });
}

The value() family methods always retrieve a value. If there is no value, a BadRequest(400) response is generated. So single value parameters are required:

1 Access to query parameter q and convert to String:
  • /?name=foofoo

  • /Bad Request(400): Missing value: "q"

2 Access to query parameter score and convert to float:
  • /?score=11.0

  • /?score=stringBad Request(400) (Type mismatch: cannot convert to number)

  • /Bad Request(400) (Required parameter score is not present)

3 Access to query parameter enabled and convert to boolean:
  • /?enabled=truetrue

  • /?enabled=stringBad Request(400) (Type mismatch: cannot convert to boolean)

  • /Bad Request(400): Missing value: "enabled"

4 Access to query parameter decimal and convert to BigDecimal:
  • /?decimal=2.32.3

  • /?decimal=stringBad Request(400) (Type mismatch: cannot convert to BigDecimal)

  • /Bad Request(400): Missing value: "decimal"

4.2.2. Default and Optional value

Default and optional value are available in two different ways:

  • Providing a default value

  • Requesting an java.uti.Optional object

Java
Kotlin
{
  get("/search", ctx -> {
    String q = ctx.query("q").value("*:*");             (1)
    return q;
  });

  get("/search", ctx -> {
    Optional<String> q = ctx.query("q").toOptional();   (2)
    return q;
  });
}
1 Access to query variable q and convert to String with a default value of :.
  • /search?q=foofoo

  • /search:

2 Access to query variable q and convert to Optional<String>:
  • /search?q=fooOptional[foo]

  • /searchOptional.empty

4.2.3. Multiple values

Multiple values are available via functions:

  • toList(): Returns a java.util.List of values

  • toSet(): Returns a java.util.Set of values

Java
Kotlin
{
  get("/", ctx -> {
    List<String> q = ctx.query("q").toList();                            (1)

    List<Integer> n = ctx.query("n").toList(Integer.class);              (2)

    List<BigDecimal> decimals = ctx.query("d").toList(BigDecimal::new);  (3)

    ...
  });
}
1 Multi-value query parameter q as List<String>:
  • /[] (empty list)

  • /?q=foo[foo]

  • /?q=foo&q=bar[foo, bar]

2 Multi-value query parameter as List<Integer>
  • /[] (empty list)

  • /?n=1[1]

  • /?n=1&n=2[1, 2]

3 Multi-value query parameter as List<BigDecimal>
  • /[] (empty list)

  • /?d=1[1]

  • /?d=1&n=2[1, 2]

4.2.4. Structured data

The Value API provides a way to traverse and parse structured data:

/?user.name=root&user.pass=pass
Traversal
Java
Kotlin
{
  get("/", ctx -> {
    Value user = ctx.query("user");                  (1)
    String name  = user.get("name").value();         (2)
    String pass  = user.get("pass").value();         (3)
    String email = user.get("email").value("none");  (4)
    ...
  }}
}
1 Get the user node
2 Get the name value from user node
3 Get the pass value from user node
4 Get the email value from user node. This is an optional value.

The get(String) takes a path and returns another value. The returning value may or may not exists.

Syntax

Structured data decoder supports dot and bracket notation:

Dot notation
?member.firstname=Pedro&member.lastname=Picapiedra
Bracket object notation
?member[firstname]=Pedro&member[lastname]=Picapiedra
Bracket array notation for tabular data
?members[0]firstname=Pedro&members[0]lastname=Picapiedra
POJO

Structured data decoder is able to reconstruct a POJO (Plain Old Java Object) from:

We are going to use a Group and Member objects to demonstrate how the decoder works:

Example
Java
Kotlin
class Member {
  public final String firstname;
  public final String lastName;

  public Member(String firstname, String lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
  }
}

class Group {
  public final String id;
  public final List<Member> members;

  public Member(String id, List<Member> members) {
    this.id = id;
    this.members = members;
  }
}
Member parsing example:
/?firstname=Pedro&lastName=Picapiedra
Java
Kotlin
{
  get("/", ctx -> {
    Member member = ctx.query(Member.class);
    ...
  });
}
Member parsing example from base node:
/?member.firstname=Pedro&member.lastName=Picapiedra
Java
Kotlin
{
  get("/", ctx -> {
    Member member = ctx.query("member").to(Member.class);
    ...
  });
}

Tabular data uses the bracket array notation:

Member as tabular data:
/?[0]firstname=Pedro&[0]lastName=Picapiedra&[1]firstname=Pablo&[2]lastname=Marmol
Java
Kotlin
{
  get("/", ctx -> {
    List<Member> members = ctx.query().toList(Member.class);
    ...
  });
}
Group with members as tabular data:
/?id=flintstones&members[0]firstname=Pedro&members[0]lastName=Picapiedra
Java
Kotlin
{
  get("/", ctx -> {
    Group group = ctx.query(Group.class);
    ...
  });
}

The target POJO must follow one of these rules:

  • Has a zero argguments/default constructor, or

  • Has only one constructor

  • Has multiple constructors, but only one is annotated with Inject

The decoder matches HTTP parameters in the following order:

  • As constructor arguments

  • As setter method

HTTP parameter name which are not a valid Java identifier must be annotated with Named:

Java
Kotlin
class Member {
  public final String firstname;

  public final String lastname;

  public Member(@Named("first-name") String firstname, @Named("last-name") String lastname) {
    ....
  }
}

♡♡

4.3. Request Body

Raw request body is available via body() method:

Java
Kotlin
{
  post("/string", ctx -> {
    String body = ctx.body().value();        (1)
    ...
  });

  post("/bytes", ctx -> {
    byte[] body = ctx.body().bytes();        (2)
    ...
  });

  post("/stream", ctx -> {
    InputStream body = ctx.body().stream();  (3)
    ...
  });
}
1 HTTP Body as String
2 HTTP Body as byte array
3 HTTP Body as InputStream

This give us the raw body.

4.3.1. Message Decoder

Request body parsing is achieved using the MessageDecoder functional interface.

public interface MessageDecoder {

  <T> T decode(Context ctx, Type type) throws Exception;
}

MessageDecoder has a single decode method that takes two input arguments: (context, type) and returns a single result of the given type.

JSON example:
Java
Kotlin
{
  FavoriteJson lib = new FavoriteJson();           (1)

  decoder(MediaType.json, (ctx, type) -> {          (2)

    byte[] body = ctx.body().bytes();              (3)

    return lib.fromJson(body, type);               (4)
  });

  post("/", ctx -> {
    MyObject myObject = ctx.body(MyObject.class);  (5)
  });
}
1 Choose your favorite json library
2 Check if the Content-Type header matches application/json
3 Ready the body as byte[]
4 Parse the body and use the requested type
5 Route handler now call the body(Type) function to trigger the decoder function

Jooby comes with a json decoder built on top of Jackson:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-jackson</artifactId>
  <version>2.1.0</version>
</dependency>

4.4. Response Body

Response body is generated from handler function:

Response body
Java
Kotlin
{
  get("/", ctx -> {
    ctx.setResponseCode(200);                  (1)

    ctx.setResponseType(MediaType.text);        (2)

    ctx.setResponseHeader("Date", new Date());  (3)

    return "Response";                          (4)
  });
}
1 Set status code to OK(200). This is the default status code
2 Set content-type to text/plain. This is the default content-type
3 Set the date header
4 Send a Response string to the client

4.4.1. Message Encoder

Response enconding is achieved using the MessageEncoder functional interface.

public interface MessageEncoder {

  byte[] encode(@Nonnull Context ctx, @Nonnull Object value) throws Exception;
}

MessageEncoder has a single encode method that accepts two input arguments: (context, result) and produces a result.

JSON example:
Java
Kotlin
{
  FavoriteJson lib = new FavoriteJson();           (1)

  encoder(MediaType.json, (ctx, result) -> {      (2)

    String json = lib.toJson(result);              (3)

    ctx.setDefaultResponseType(MediaType.json);    (4)

    return json;                                   (5)
  });

  get("/item", ctx -> {
    MyObject myObject = ...;
    return myObject;                               (6)
  });
}
1 Choose your favorite json library
2 Check if the Accept header matches application/json
3 Convert result to JSON
4 Set default Content-Type to application/json
5 Produces JSON response
6 Route handler returns a user defined type

Jooby comes with a json encoder built on top of Jackson:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-jackson</artifactId>
  <version>2.1.0</version>
</dependency>

5. Static Files

Static files are available via assets(String) route. The assets route supports classpath and file-system resources.

Classpath resources:
Java
Kotlin
{
  assets("/static/*"); (1)
}
1 Map all the incoming request starting with /static/ to the root of classpath
  • GET /static/index.html/index.html

  • GET /static/js/file.js/js/file.js

  • GET /static/css/styles.css/css/styles.css

File system resources:
Java
Kotlin
{
  assets("/static/*", Paths.get("static")); (1)
}
1 Map all the incoming request starting with /static/ to a file system directory www
  • GET /static/index.htmlwww/index.html

  • GET /static/js/file.jswww/js/file.js

  • GET /static/css/styles.csswww/css/styles.css

Individual file mapping is supported too:

Classpath:
File system
{
  assets("/myfile.js", "/static/myfile.js");
}

5.1. Static Site

The assets route works for static sites too. Just need to use a special path mapping:

Classpath resources:
Java
Kotlin
{
  Path docs = Paths.get("docs"); (1)
  assets("/docs/?*");            (2)
}
1 Serve from docs directory
2 Use the /?* mapping

The key difference is the /?* mapping. This mapping add support for base root mapping:

  • GET /docs/docs/index.html

  • GET /docs/index.html/docs/index.html

  • GET /docs/about.html/docs/about.html

  • GET /docs/note/docs/note/index.html

5.2. SPAs

The assets route works for single page applications (SPAs) too. Just need to use a special path mapping plus a fallback asset:

Classpath resources:
Java
Kotlin
{
  AssetSource docs = AssetSource.create(Paths.get("docs")); (1)
  assets("/docs/?*", new AssetHandler("index.html", docs)); (2)
}
1 Serve from docs directory
2 Use the /?* mapping and uses index.html as fallback asset

SPAs mode never generates a NOT FOUND (404) response, unresolved assets fallback to index.html

5.3. Options

The AssetHandler automatically handles E-Tag and Las-Modified headers. You can turn control these headers programmatically:

Asset handler options:
Java
Kotlin
{
  AssetSource www = AssetSource.create(Paths.get("www"));
  assets("/static/*", new AssetHandler(www)
    .setLastModified(false)
    .setEtag(false)
  );
}

The maxAge option set a Cache-Control header:

Cache control:
Java
Kotlin
{
  AssetSource www = AssetSource.create(Paths.get("www"));
  assets("/static/*", new AssetHandler(www)
    .setMaxAge(Duration.ofDays(365))
  );
}

6. Templates

Templates are available via ModelAndView and requires a TemplateEngine implementation.

Java
Kotlin
{
  install(new MyTemplateEngineModule());            (1)

  get("/", ctx -> {
    Map<String, Object> model = ...;                (2)
    return new ModelAndModel("index.html", model);  (3)
  });
}
1 Install a template engine
2 Build the view model
3 Returns a ModelAndView instance

6.1. Template Engine

Template engine does the view rendering/encoding. Template engine extends a MessageEncoder by accepting a ModelAndView instance and produces a String result.

The extensions() method list the number of file extension that a template engine supports. Default file extension is: .html.

The file extension is used to locate the template engine, when a file extension isn’t supported an IllegalArgumentException is thrown.

The file extension allow us to use/mix multiple template engines too:

Java
Kotlin
{
  install(new HandlebarsModule());                 (1)
  install(new FreemarkerModule());                 (2)

  get("/first", ctx -> {
    return new ModelAndModel("index.hbs", model);  (3)
  });

  get("/second", ctx -> {
    return new ModelAndModel("index.ftl", model);  (4)
  });
}
1 Install Handlebars
2 Install Freemarker
3 Render using Handlebars, .hbs extension
4 Render using Freemarker, .ftl extension

Checkout all the available template engines provided by Jooby.

7. Execution Model

Jooby is a flexible performant microframework providing both blocking and non-blocking APIs for building web applications in Java and Kotlin.

In this chapter we are going to learn about Jooby execution model, more specifically:

  • Execute code on the event loop

  • Safely execution of blocking code

  • Working with non-blocking types, like: CompletableFuture, Reactive Streams, Kotlin Coroutines, etc.

7.1. Mode

7.1.1. Event Loop

The EVENT_LOOP mode allows us to run a route handler from the event loop (a.k.a as non-blocking mode).

Java
Kotlin
import static io.jooby.ExecutionMode.EVENT_LOOP;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/", ctx -> "I'm non-blocking!" );
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App::new);
  }
}

The EVENT_LOOP mode is the more advanced execution mode and requires you carefully design and implement your application due to that BLOCKING IS NOT ALLOWED

What if you need to block?

The dispatch(Runnable) operator moves execution to a worker executor which allows to do blocking calls:

Java
Kotlin
import static io.jooby.ExecutionMode.EVENT_LOOP;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/", ctx -> {
      return "I'm non-blocking!";
    });

    dispatch(() -> {
      // All the routes defined here are allowed to block:

      get("/db-list", ctx -> {
        /** Safe to block! */
        Object result = ...; // Remote service, db call, etc..
        return result;
      });

    });
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App::new);
  }
}

By default, the dispatch(Runnable) operator moves execution to the server worker executor (executor provided by web server).

You can provide your own worker executor at application level or at dispatch level:

Java
Kotlin
import static io.jooby.ExecutionMode.EVENT_LOOP;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    // Application level executor
    worker(Executors.newCachedThreadPool());

    // Dispatch to application level executor which is cached thread pool
    dispatch(() -> {
      ...
    });

    // Dispatch to a explicit executor
    Executor cpuIntensive = Executors.newSingleThreadExecutor();
    dispatch(cpuIntesive, () -> {
      ...
    });
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App:new);
  }
}

7.1.2. Worker

The WORKER mode allows us to do blocking calls from a route handler (a.k.a blocking mode). You just write code without worrying about blocking calls.

Java
Kotlin
import static io.jooby.ExecutionMode.WORKER;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/", ctx -> {
      /** Safe to block! */
      Object result = // Remote service, db call, etc..
      return result;
    });
  }

  public static void main(String[] args) {
    runApp(args, WORKER, App::new);
  }
}

Like with EVENT_LOOP mode, you can provide your own worker executor:

Java
Kotlin
import static io.jooby.ExecutionMode.WORKER;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    worker(Executors.newCachedThreadPool());

    get("/", ctx -> {
      /** Safe to block from cached thread pool! */
      Object result = // Remote service, db call, etc..
      return result;
    });
  }

  public static void main(String[] args) {
    runApp(args, WORKER, App::new);
  }
}

While running in WORKER mode, Jooby internally does the dispatch call to the worker executor. This is done per route, not globally.

7.1.3. Default

The DEFAULT execution mode is a mix between WORKER and EVENT_LOOP modes. This (as name implies) is the default execution mode in Jooby.

Jooby detects the route response type and determines which execution mode fits better.

If the response type is considered non-blocking, then it uses the event loop. Otherwise, it uses the worker executor.

A response type is considered non-blocking when route handler produces:

Java
Kotlin
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/non-blocking", ctx -> {
      return CompletableFuture
          .supplyAsync(() -> "I'm non-blocking!")  (1)
    });

    get("/blocking", ctx -> {
      return "I'm blocking";                       (2)
    });
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 CompletableFuture is a non-blocking type, run in event loop
2 String is a blocking type, run in worker executor

You are free to use non-blocking types in all the other execution mode too. Non-blocking response types are not specific to the default mode execution. All the default mode does with them is to dispatch or not to a worker executor.

7.2. Worker Executor

This section described some details about the default worker executor provided by web server. The worker executor is used when:

Each web server provides a default worker executor:

  • Netty: The Netty server implementation multiply the number of available processors (with a minimum of 2) by 8.

workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8

For example 8 cores gives us 64 worker threads.

  • Undertow: The Undertow server implementation multiply the number of available processors (with a minimum of 2) by 8.

workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8

For example 8 cores gives us 64 worker threads.

  • Jetty: The Jetty server implementation uses the default configuration with 200 worker threads.

These are sensible defaults suggested by the server implementation. If you need to increase/decrease worker threads:

Java
Kotlin
{
  configureServer(server -> {
    server.workerThreads(Number);
  });
}

8. Responses

This chapter covers some special response types, like raw responses, streaming, file download, non-blocking, etc…​

8.1. Raw

Raw responses are NOT processed by a message encoder. These response types are considered raw:

  • String/CharSequence

  • byte[]

  • java.nio.ByteBuffer/io.netty.buffer.ByteBuf

  • java.io.File/java.io.InputStream/java.nio.file.Path/java.nio.channels.FileChannel

Generate a JSON String from handler
Java
Kotlin
{
  get("/json", ctx -> {
    ctx.setContentType(MediaType.json);
    return "{\"message\": \"Hello Raw Response\"}";
  });
}

No matter if there is a JSON encoder installed, a raw response is always send directly to client.

8.2. Streaming / Chunked

Streaming/chunked API is available via:

Only one of these methods must be call it per request. At the time you call one of these methods Jooby automatically adds the Transfer-Encoding: chunked header when Content-Length is missing.

All the three APIs have a close method. You must call it once you finish.

Writer example
Java
Kotlin
{
  get("/chunk", ctx -> {
    try(Writer writer = ctx.responseWriter()) { (1)
      writer.write("chunk1");                   (2)
      ...
      writer.write("chunkN");
    }

    return ctx;                                 (3)
  });
}
1 Get the Writer inside a try-with-resources statement, so close it automatically.
2 Write chunks
3 Return the Context

There is an overloaded version (for Java mainly) that let you skip the try-with-resources and automatically close the writer/stream:

Writer example
Java
Kotlin
{
  get("/chunk", ctx -> {
    return ctx.responseWriter(writer -> { (1)
      writer.write("chunk1");             (2)
      ...
      writer.write("chunkN");
    });
  });
}

8.3. File download

The AttachedFile is used to generate file downloads, i.e. responses with Content-Disposition header.

File download example
Java
Kotlin
{
  get("/download-file", ctx -> {
    Path source = Paths.get("logo.png");
    return new AttachedFile(source);               (1)
  });

  get("/download-stream", ctx -> {
    InputStream source = ...;
    return new AttachedFile("myfile.txt", source); (2)
  });
}
1 Send a download from an InputStream
2 Send a download from a File

8.4. NonBlocking

Non-blocking responses are a new feature of Jooby 2.x.

From user point of view there is nothing special about them, you just write your route handler as usually do with blocking types.

In Jooby 1.x we were forced to produces directly/indirectly a Deferred result. All that is gone now, we don’t need a custom type to do the integration.

Before we jump to each of the supported types, we need to learn what occurs in the pipeline when there is a non-blocking route handler.

In event loop
Java
Kotlin
{
  mode(EVENT_LOOP);                 (1)

  get("/non-blocking", ctx -> {

    ...                             (2)

    return CompletableFuture        (3)
        .supplyAsync(() -> {
          ...                       (4)
        });
  })
}
1 App run in event loop
2 Route block run in event loop. No blocking code is permitted
3 Value is provided from event loop. No blocking code is permitted
4 Value is computed/produces from completable future context

Running your App in worker mode works identically, except for we are able to do blocking calls:

In worker mode
Java
Kotlin
{
  mode(WORKER);                     (1)

  get("/blocking", ctx -> {

    ...                             (2)

    return CompletableFuture        (3)
        .supplyAsync(() -> {
          ...                       (4)
        });
  })
}
1 App run in worker mode
2 Route block run in worker mode. Blocking code is permitted
3 Value is provided from worker mode. Blocking code is permitted
4 Value is computed/produces from completable future context

Running your App in default mode works identically to running in the event loop mode:

In default mode
Java
Kotlin
{
  mode(DEFAULT);                    (1)

  get("/non-blocking", ctx -> {

    ...                             (2)

    return CompletableFuture        (3)
        .supplyAsync(() -> {
          ...                       (4)
        });
  })
}
1 App run in event loop
2 Route block run in event loop. No blocking code is permitted
3 Value is provided from event loop. No blocking code is permitted
4 Value is computed/produces from completable future context

The default mode mimics the event loop mode execution when route produces a non-blocking type.

8.4.1. Limitations

While writing non-blocking/reactive responses we should avoid the use of Jooby filters: before and after.

In most use cases they won’t work, so it is preferred to avoid them while programming non-blocking/reactive responses.

On non-blocking/reactive responses there is always a "dispatch call". This call moves execution to somewhere else (usually a different thread). Because of this is almost impossible to ensure the execution of pipeline.

If you have a non-blocking route handler, it is better to not have any type of filter in the pipeline

The alternative option is to write cross-cutting concerns we usually put inside a filter using the non-blocking API.

8.4.2. CompletableFuture

CompletableFuture is considered a non-blocking type which is able to produces a single result:

Java
Kotlin
{
  get("/non-blocking", ctx -> {
    return CompletableFuture
        .supplyAsync(() -> "Completable Future!")
        .thenApply(it -> "Hello " + it);
  })
}

8.4.3. RxJava

1) Add the RxJava dependency:

Maven
Gradle
<dependency>
  <groupId>io.reactivex.rxjava2</groupId>
  <artifactId>rxjava</artifactId>
  <version>2.2.12</version>
</dependency>

2) Write code:

Single
Java
Kotlin
{
  get("/non-blocking", ctx -> {
    return Single
        .fromCallable(() -> "Single")
        .map(it -> "Hello " + it);
  })
}
Flowable
Java
Kotlin
{
  get("/non-blocking", ctx -> {
    return Flowable.range(1, 10)
        .map(it -> it + ", ");
  })
}

For Flowable, Jooby builds a chunked response. That:

  1. Set the Transfer-Encoding: chunked header

  2. Each item means new chunk send it to client

8.4.4. Reactor

1) Add the Reactor dependency:

Maven
Gradle
<dependency>
  <groupId>io.projectreactor</groupId>
  <artifactId>reactor-core</artifactId>
  <version>3.2.11.RELEASE</version>
</dependency>

2) Write code:

Mono
Java
Kotlin
{
  get("/non-blocking", ctx -> {
    return Mono
        .fromCallable(() -> "Mono")
        .map(it -> "Hello " + it);
  })
}
Flux
Java
Kotlin
{
  get("/non-blocking", ctx -> {
    return Flux.range(1, 10)
        .map(it -> it + ", ");
  })
}

For Flux, Jooby builds a chunked response. That:

  1. Set the Transfer-Encoding: chunked header

  2. Each item means new chunk send it to client

8.4.5. Kotlin Coroutines

Probably one of most exciting new features of Jooby 2.x is the builtin integration with Kotlin Coroutines:

Coroutine handler:
Normal handler:
{
  coroutine {
    get("/") {         (1)
      ctx.pathString()
    }
  }
}
1 Coroutine route
2 Normal route

Also, if you try to call a suspending function from normal handler, Kotlin complains about:

Not allowed it:
{
  get("/") {
    delay(100)      (1)
    "..."
  }
}
1 Suspend function 'delay' should be called only from a coroutine or another suspend function

Now, if we wrap the route with the coroutine router:

Hello Coroutines
{
  coroutine {
    get("/") {
      delay(100)           (1)
      "Hello Coroutines!"  (2)
    }
  }
}
1 Call a suspending function
2 Send response to client
Here is another example with an extension and suspending function:
{
  coroutine {
    get("/") {
      ctx.doSomething()         (1)
    }
  }
}

suspend fun Context.doSomething(): String {
  delay(100)                  (2)
  return "Hello Coroutines!"  (3)
}
1 Call extension suspending function
2 Call a suspending function or do a blocking call
3 Send response to client

A coroutine works like any of the other non-blocking types. You start Jooby using the event loop or default mode, Jooby detects we produce a coroutine and creates a coroutine context to execute it.

Jooby uses the worker executor to creates a coroutine context. As described in worker executor section this is provided by the web server implementation unless you provided your own.

Coroutines with custom executor:
{
  worker(Executors.newCachedThreadPool())

  coroutine {
    get("/") {
      val n = 5 * 5        (1)
      delay(100)           (2)
      "Hello Coroutines!"  (3)
    }
  }
}
1 Statement run in the worker executor (cached thread pool)
2 Call a suspending function
3 Produces a response

Coroutines always run in the worker executor. There is an experimental API where coroutines run in the caller thread(event loop in this case) until a suspending function is found.

Jooby allows you to use this experimental API by setting the coroutineStart option:

UNDISPATCHED
{
  coroutine (CoroutineStart.UNDISPATCHED) {
    get("/") {
      val n = 5 * 5        (1)
      delay(100)           (2)
      "Hello Coroutines!"  (3)
    }
  }
}
1 Statement run in the event loop (caller thread)
2 Call a suspending function and dispatch to worker executor
3 Produces a response from worker executor

♡ ♡!

8.5. Send methods

Jooby provides a family of send() methods that produces a response via side effects.

send text
Java
Kotlin
{
  get("/", ctx -> {
    return ctx.send("Hello World!");
  });
}

Beside we operate via side effects, the route still returns something. This is required because a route handler is a function which always produces a result.

All the send methods returns the current Context, this signal Jooby that we want to operate via side effects ignoring the output of the route handler.

Family of send methods include:

9. MVC API

MVC API is an alternative way to define routes in Jooby. It uses annotations and byte code generation to define and execute routes.

The package io.jooby.annotations contains all the annotations available for MVC routes.

MVC API:
Java
Kotlin
import io.jooby.annotations.*;

@Path("/mvc")                  (1)
public class Controller {

  @GET                         (2)
  public String sayHi() {
    return "Hello Mvc!";
  }
}

public class App extends Jooby {

  {
    mvc(new Controller());   (3)
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Set a path pattern. The @Path annotation is enable at class or method level
2 Add a HTTP method
3 Register/install the controller in the main application

9.1. Getting Started

To create a new MVC project open the jooby console and type:

jooby create myapp --mvc

The jooby console takes care of all configuration steps required by the annotation processing tool.

9.2. Parameters

HTTP parameter provision is available via *Param annotations.

9.2.1. Header

Provisioning of headers is available via HeaderParam annotation:

Headers
Java
Kotlin
public class MyController {

  @GET
  public Object provisioning(@HeaderParam String token) {  (1)
    ...
  }
}
1 Access to HTTP header named token

Compared to JAX-RS the parameter name on @*Param annotation is completely optional, but required for non valid Java names:

Non valid Java name
Java
Kotlin
public class MyController {

  @GET
  public Object provisioning(@HeaderParam("Last-Modified-Since") long lastModifiedSince) {
    ...
  }
}

Provisioning of cookies is available via CookieParam annotation:

Cookies
Java
Kotlin
public class MyController {

  @GET
  public Object provisioning(@CookieParam String token) {  (1)
    ...
  }
}
1 Access to cookie named token

Compared to JAX-RS the parameter name on @*Param annotation is completely optional, but required for non valid Java names:

Non valid Java name
Java
Kotlin
public class MyController {

  @GET
  public Object provisioning(@CookieParam("token-id") String tokenId) {
    ...
  }
}

9.2.3. Path

For path parameters the PathParam annotation is required:

PathParam
Java
Kotlin
public class MyController {

  @Path("/{id}")
  public Object provisioning(@PathParam String id) {
    ...
  }
}

9.2.4. Query

For query parameters the QueryParam annotation is required:

QueryParam
Java
Kotlin
public class MyController {

  @Path("/")
  public Object provisioning(@QueryParam String q) {
    ...
  }
}

9.2.5. Formdata/Multipart

For formdata/multipart parameters the FormParam annotation is required:

QueryParam
Java
Kotlin
public class MyController {

  @Path("/")
  @POST
  public Object provisioning(@FormParam String username) {
    ...
  }
}

9.2.6. Body

Body parameter doesn’t require an annotation:

HTTP Body
Java
Kotlin
public class MyController {

  @Path("/")
  @POST
  public Object provisioning(MyObject body) {
    ...
  }
}

9.2.7. Flash

Provisioning of flash attribute is available via FlashParam annotation:

Flash
Java
Kotlin
public class MyController {

  @GET
  public Object provisioning(@FlashParam String success) {  (1)
    ...
  }
}
1 Access to flash named success

9.3. Registration

Mvc routes need to be registered (no classpath scanning). Registration is done from your application class:

Simple MVC route registration
Java
Kotlin
public class App extends Jooby {
  {
    mvc(new MyController());
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}

The mvc(Object) install the mvc route. As showed in the example there is no dependency injection involved, you just instantiate a MVC route and register.

Class MVC route registration
Java
Kotlin
public class App extends Jooby {
  {
    mvc(MyController.class);
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}

The mvc(Class) does the same job, but delegates route instantiation to a dependency injection framework of your choice.

Jooby 1.x was built around Guice, this is not the case for 2.x. The entire project was built without dependency injection. This make DI optional and at same time give you freedom to choose the one you like most.
Provider MVC route registration
Java
Kotlin
import javax.inject.Provider;

public class App extends Jooby {
  {
    Provider<MyController> provider = ...;

    mvc(MyController.class, provider);
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}

The mvc(Provider) does the same job, might or might not delegate instantiation to a dependency injection framework but most important let you control lifecycle of MVC routes (Singleton vs Non-Singleton routes).

9.4. Execution model

The MVC routes follows the execution model described in Execution Model. To run application logic in the EVENT_LOOP:

EventLoop MVC route
Java
Kotlin
public class App extends Jooby {
  {
    mvc(new MyController());
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App::new);  (1)
  }
}
1 Start the application in the EVENT_LOOP execution mode

Similarly, if you need to run all mvc routes in the WORKER execution mode:

Worker mode MVC route
Java
Kotlin
public class App extends Jooby {
  {
    dispatch(() -> {
      mvc(new MyBlockingController());  (1)
    });
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App::new);
  }
}
1 Wrap the controller using the dispatch operator

One drawback with this approach is that the entire controller is now going to be executed in the worker or custom executor. For more fine grain control use the Dispatch annotation:

Dispatch annotation
Java
Kotlin
public class MyController {
  @GET("/nonblocking")
  public String nonblocking() {  (1)
    return "I'm nonblocking";
  }

  @GET("/blocking")
  @Dispatch
  public String blocking() {     (2)
    return "I'm blocking";
  }
}
1 MVC route run in EVENT_LOOP mode. Blocking is NOT allowed it.
2 MVC route run in WORKER mode. Blocking is allowed it.

The Dispatch annotation supports custom executor using an executor name.

Dispatch to custom executor
Java
Kotlin
public class MyController {
  @GET("/blocking")
  @Dispatch("single")         (1)
  public String blocking() {
    return "I'm blocking";
  }
}
1 Dispatch to an executor named it single

Executor must be registered using via services or executor utility method:

Custom executor registration
Java
Kotlin
{
  executor("single", Executors.newSingleThreadExecutor());

  mvc(new MyController());
}

The executor must be registered before the MVC route/controller.

9.5. Suspend functions

For Kotlin users MVC routes are allowed to use suspend functions

Kotlin Coroutines
class SuspendMvc {
  @GET
  @Path("/delay")
  suspend fun delayed(ctx: Context): String {
    delay(100)
    return ctx.pathString()
  }
}

fun main(args: Array<String>) {
  runApp(args) {
    use(SuspendMvc())
  }
}

9.6. JAX-RS Annotations

Alternative you can use JAX-RS annotations to define MVC routes.

Resource
Java
Kotlin
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/jaxrs")
public class Resource {

  @GET
  public String getIt() {
    return "Got it!";
  }
}

Annotations work exactly like the Jooby MVC annotations, but keep in mind we don’t implement the JAX-RS specification and there is no immediate plan to do it.

The main reason to support JAX-RS annotations is to let you plug-in third-party tools that rely on them (mostly annotations processors).

10. Error Handler

Jooby catches application exception using the javadoc:ErrorHandler class. The DEFAULT error handler produces simple HTML page or JSON based on the value of the ACCEPT header and log the exception.

HTML:
Not Found
message: Page not found
status code: 404
JSON:
{
  "message": "Page not found",
  "status": 404,
  "reason": "Not Found"
}
Log:
GET /xx 404 Not Found
io.jooby.StatusCodeException: Not found
	at ...
	at ...
	at ...
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

The javadoc:StatusCodeException works as generic exception that let you specify an status code.

throw new StatusCodeException(StatusCode.FORBIDDEN);

throw new StatusCodeException(StatusCode.NOT_FOUND);

...

These exception types have a default status code:

  • IllegalArgumentException: BAD_REQUEST(400) (or sub-classes of it)

  • NoSuchElementException: BAD_REQUEST(400) (or sub-classes of it)

  • FileNotFound: NOT_FOUND(404) (or sub-classes of it)

  • Exception: SERVER_ERROR(500) (or sub-classes of it)

To set a custom status code, an entry should be added it to the error code map:

{
  errorCode(MyException.class, StatusCode.XXX);
}

10.1. Custom Error Handler

You can provide your own error handler using the error(ErrorHandler) method:

Error Handler
Java
Kotlin
{
  error((ctx, cause, statusCode) -> {                                      (1)
    Router router = ctx.getRouter();
    router.getLog().error("found `{}` error", statusCode.value(), cause);  (2)
    ctx.setResponseStatusCode(statusCode);
    ctx.send("found `" + statusCode.value() + "` error");                  (3)
  });
}
1 Add a global/catch-all exception handler
2 Log the error to logging system
3 Send an error response to the client

You can use the render(Object) object which looks for a registered MessageEncoder or TemplateEngine.

The next example produces a HTML or JSON response based on the value of the Accept header.

Error with content negotiation
Java
Kotlin
import static io.jooby.Medio.json;
import static io.jooby.Medio.html;

{
  install(new MyTemplateEngineModule());  (1)

  install(new MyJsonModule());            (2)

  error((ctx, cause, statusCode) -> {
    Router router = ctx.getRouter();
    router.getLog().error("found `{}` error", statusCode.value(), cause);

    MediaType accept = ctx.accepts(json); (3)
    if (json.equals(accept)) {            (4)
      Map error = ...;
      ctx.render(error);
    } else {
      // fallback to html
      Map error = ...;
      ctx.render(new ModelAndView("error.template", error));
    }
  });
}
1 Install one of the available template engines
2 Install one of the available json modules
3 Test if the accept header matches the application/json content type
4 Render json or fallback to html

10.2. Catch by Code

In addition to the generic/global error handler you can catch specific status code:

Status Code Error Handler
Java
Kotlin
import static io.jooby.StatusCode.NOT_FOUND;
{
  error(NOT_FOUND, (ctx, cause, statusCode) -> {
    ctx.send(statusCode);   (1)
  });
}
1 Send 404 response to the client

Here we kind of silence all the 404 response due we don’t log anything and send an empty response.

The send(StatusCode) method send an empty response to the client

10.3. Catch by Exception

In addition to the generic/global error handler you can catch specific exception type:

Exception Handler
Java
Kotlin
{
  error(MyException.class, (ctx, cause, statusCode) -> {
    // log and process MyException
  });
}

11. Handlers

This section describes some built-in handler provided by Jooby.

11.1. CorsHandler

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) than its own origin.

Jooby supports CORS out of the box. By default, CORS requests will be rejected. To enable processing of CORS requests, use the CorsHandler:

CorsExample
Java
Kotlin
import io.jooby.Jooby;
import io.jooby.CorsHandler;
...
{

  decorator(new CorsHandler()); (1)

  path("/api", () -> {
    // API methods
  });
}
1 Install CorsHandler with defaults options

Default options are:

  • origin: *

  • credentials: true

  • allowed methods: GET, POST

  • allowed headers: X-Requested-With, Content-Type, Accept and Origin

  • max age: 30m;

To customize default options use Cors:

Cors options
Java
Kotlin
import io.jooby.Jooby;
import io.jooby.CorsHandler;
...
{
  Cors cors = new Cors()
     .setMethods("GET", "POST", "PUT");      (1)

  decorator(new CorsHandler(cors));          (2)

  path("/api", () -> {
    // API methods
  });
}
1 Specify allowed methods
2 Pass cors options to cors handler

Optionally cors options can be specified in the application configuration file:

application.conf
cors {
  origin: "*"
  credentials: true
  methods: [GET, POST],
  headers: [Content-Type],
  maxAge: 30m
  exposedHeaders: [Custom-Header]
}
Loading options
Java
Kotlin
import io.jooby.Jooby;
import io.jooby.CorsHandler;
...
{
  Cors cors = Cors.from(getConfig());  (1)

  decorator(new CorsHandler(cors));

  path("/api", () -> {
    // API methods
  });
}
1 Load cors options from application configuration file

11.2. HeadHandler

Jooby doesn’t support HTTP HEAD requests by default. To support them you have two options:

  • Use the built-in HeadHandler

  • Write your own head handler

The HeadHandler supports HEAD requests over existing GET handlers.

Head Example
Java
Kotlin
import io.jooby.Jooby;
import io.jooby.HeadHandler;
...
{

  decorator(new HeadHandler()); (1)

  get("/", ctx -> {
    ...
  });
}
1 Install HeadHandler

HEAD / produces an empty response with a Content-Length header (when possible) and any other header produce it by the GET handler.

The GET handler is executed but produces an empty response.

11.3. TraceHandler

Jooby doesn’t support HTTP Trace requests by default. To support them you have two options:

  • Use the built-in TraceHandler

  • Write your own trace handler

The TraceHandler supports TRACE requests over existing handlers.

Head Example
Java
Kotlin
import io.jooby.Jooby;
import io.jooby.TraceHandler;
...
{

  decorator(new TraceHandler()); (1)

  get("/", ctx -> {
    ...
  });
}
1 Install TraceHandler

TRACE / performs a message loop-back test along the path to the target resource, providing a useful debugging mechanism.

12. Configuration

Application configuration is based on config library. Configuration can by default be provided in either Java properties, JSON, and HOCON files.

Jooby allows overriding any property via system properties or environment variables.

12.1. Environment

The application environment is available via the Env class, which allows specifying one or many unique environment names.

The active environment names serve the purpose of allowing loading different configuration files depending on the environment. Also, Extension modules might configure application services differently depending on the environment too. For example: turn on/off caches, reload files, etc.

Initializing the Environment
Java
Kotlin
{
  Environment env = getEnvironment();
}

The active environment names property is set in one of this way:

  • As program argument: java -jar myapp.jar application.env=foo,bar; or just java -jar myapp.jar foo,bar

This method works as long you start the application using one of the runApp methods
  • As system property: java -Dapplication.env=foo,bar -jar myapp.jar

  • As environment variable: application.env=foo,bar

The getEnvironment() loads the default environment.

12.2. Default Environment

The default environment is available via loadEnvironment(ClassLoader) method.

This method search for an application.conf file in three location (first-listed are higher priority):

  • ${user.dir}/conf. This is a file system location, useful is you want to externalize configuration (outside of jar file)

  • ${user.dir}. This is a file system location, useful is you want to externalize configuration (outside of jar file)

  • classpath:// (root of classpath). No external configuration, configuration file lives inside the jar file

We use $user.dir to reference System.getProperty("user.dir"). This system property is set by the JVM at application startup time. It represent the current directory from where the JVM was launch it.
File system loading
└── conf
    └── application.conf
└── myapp.jar

A call to:

  Environment env = getEnvironment();

Loads the application.conf from conf directory. You get the same thing if you move the application.conf to myapp.jar directory.

Classpath loading
└── myapp.jar
   └── application.conf (file inside jar)
Jooby favors file system property loading over classpath property loading. So, if there is a property file either in the current directory or conf directory it hides the same file available in the classpath.

12.3. Overrides

Property overrides is done in multiple ways (first-listed are higher priority):

  • Program arguments

  • System properties

  • Environment variables

  • Environment property file

  • Property file

application.conf
foo = foo
Property access
Java
Kotlin
{
  Environment env = getEnvironment();                (1)
  Config conf = env.getConfig();             (2)
  System.out.println(conf.getString("foo")); (3)
}
1 Get environment
2 Get configuration
3 Get foo property and prints foo

At runtime you can override properties using:

Program argument
java -jar myapp.jar foo=argument

Example prints: argument

System property
java -Dfoo=sysprop -jar myapp.jar

Prints: syspro

Environment variable
foo=envar java -jar myapp.jar

Prints: envar

If you have multiple properties to override, it is probably better to collect all them into a new file and use active environment name to select them.

Environment property file
└── application.conf
└── application.prod.conf
application.conf
foo = foo
bar = devbar
application.prod.conf
bar = prodbar
Run with prod environment
java -jar my.app application.env=prod

Or just

java -jar my.app prod
You only need to override the properties that changes between environment not all the properties.

The application.conf defines two properties : foo and bar, while the environment property file defines only bar.

For Multiple environment activation you need to separate them with , (comma):

Run with prod and cloud environment
 java -jar my.app application.env=prod,cloud

12.4. Custom environment

Custom configuration and environment are available too using:

Environment options
Java
Kotlin
{
  Environment env = setEnvironmentOptions(new EnvOptions() (1)
    .setFilename("myapp.conf")
  )
}
1 Load myapp.conf using the loading and precedence mechanism described before

The setEnvironmentOptions(EnvironmentOptions) method loads, set and returns the environment.

To skip/ignore Jooby loading and precedence mechanism, just instantiate and set the environment:

Direct instantiation
Java
Kotlin
{
  Config conf = ConfigFatory.load("/path/to/myapp.conf");  (1)
  Environment env = new Env(customConfig, "prod");         (2)
  setEnvironment(env);                                     (3)
}
1 Loads and parses configuration
2 Create a new environment with configuration and (optionally) active names
3 Set environment on Jooby instance
Custom configuration is very flexible. You can reuse Jooby mechanism or provide your own. The only thing to keep in mind is that environment setting must be done at very early stage, before starting the application.

12.5. Logging

Jooby uses Slf4j for logging which give you some flexibility for choosing the logging framework.

12.5.1. Logback

The Logback is probably the first alternative for Slf4j due its natively implements the SLF4J API. Follow the next steps to use logback in your project:

1) Add dependency

Maven
Gradle
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

That’s all! Slf4j is going to redirect log message to logback.

12.5.2. Log4j2

The Log4j2 project is another good alternative for logging. Follow the next steps to use logback in your project:

1) Add dependencies

Maven
Gradle
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j-impl</artifactId>
  <version>2.12.1</version>
</dependency>

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.12.1</version>
</dependency>
log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <Console name="stdout">
      <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable" />
    </Console>
  </Appenders>
  <Loggers>
    <Root level="INFO" additivity="true">
      <AppenderRef ref="stdout" />
    </Root>
  </Loggers>
</Configuration>

All these extensions are considered valid: .xml, .propertines, .yaml and .json. As well as log4j2 for file name.

12.5.3. Environment logging

Logging is integrated with the environment names. So it is possible to have a file name:

  • logback[.name].xml (for loggback)

  • log4j[.name].xml (for log4j2)

Jooby favors the environment specific logging configuration file over regular/normal logging configuration file.

Example
conf
└── logback.conf
└── logback.prod.conf

To use logback.prod.conf, start your application like:

java -jar myapp.jar application.env=prod

The logging configuration file per environment works as long you don’t use static loggers before application has been start it. The next example won’t work:

public class App extends Jooby {
  private static final Logger log = ...

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}

The runApp method is the one who configures the logging framework. Adding a static logger force the logging framework to configure without taking care the environment setup.

There are a couple of solution is for this:

  • use an instance logger

  • use the getLog() method of Jooby

13. Testing

This section will show you how to run unit and integration tests with Jooby.

13.1. Unit Testing

1) Add Jooby test dependency:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-test</artifactId>
  <version>2.1.0</version>
</dependency>

2) Write your application:

App
Java
Kotlin
public class App extends Jooby {
  {
    get("/", ctx -> "Easy unit testing!");
  }
}

3) Write your test:

TestApp
Java
Kotlin
import io.jooby.MockRouter;

public class TestApp {

  @Test
  public void test() {
    MockRouter router = new MockRouter(new App());
    assertEquals("OK", router.get("/").value());
  }
}

Simple and easy ♡!

The MockRouter returns the value produced by the route handler. It is possible to get access and check response metadata:

App
Java
Kotlin
public class App extends Jooby {
  {
    get("/", ctx -> ctx
        .setResponseCode(StatusCode.OK)
        .send("Easy unit testing")
    );
  }
}
Checking response metadata
Java
Kotlin
public class TestApp {

  @Test
  public void test() {
    MockRouter router = new MockRouter(new App());
    router.get("/", response -> {
      assertEquals(StatusCode.OK, response.getStatusCode());
      assertEquals("Easy unit testing", response.value(String.class));
    });
  }
}

For more complex route context interaction or responses, you can pass in a MockContext:

App
Java
Kotlin
public class App extends Jooby {
  {
    post("/", ctx -> {
      String name = ctx.form("name").value();
      return name;
    });
  }
}
Using mock context
Java
Kotlin
public class TestApp {

  @Test
  public void test() {
    MockRouter router = new MockRouter(new App());
    MockContext context = new MockContext()
        .setForm(Formdata.create()
            .put("name", "Test!")
        );
    assertEquals("Test!", router.post("/", context).value());
  }
}

Alternative you can provide your own mock context:

Mockito Context
Java
Kotlin
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class TestApp {

  @Test
  public void test() {
    Value name = mock(Value.class);
    when(name.value()).thenReturn("Test!");

    Context context = mock(Context.class);
    when(context.form("name")).thenReturn(name);

    MockRouter router = new MockRouter(new App());

    assertEquals("Test!", router.post("/", context).value());
  }
}

♡ ♡!

For MVC routes you might prefer to write a unit test using a mock library. No need to use MockRouter, but it is possible too.

13.1.1. Options

Unit testing is simple and easy in Jooby. The MockRouter let you execute the route function, while the MockContext allows you to create an light-weight and mutable context where you can set HTTP parameters, body, headers, etc.

13.2. Integration Testing

Integration tests are supported via JUnit 5 extension mechanism.

1) Add Jooby test dependency:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-test</artifactId>
  <version>2.1.0</version>
</dependency>

2) Write your application:

App
Java
Kotlin
public class App extends Jooby {
  {
    get("/", ctx -> "Easy testing!");
  }
}

3) Write your test:

TestApp
Java
Kotlin
import io.jooby.JoobyTest;

@JoobyTest(App.class)
public class TestApp {

  static OkHttpClient client = new OkHttpClient();

  @Test
  public void test() {
    Request request = new Request.Builder()
        .url("http://localhost:8911")
        .build();

    try (Response response = client.newCall(request).execute()) {
      assertEquals("Easy testing!", response.body().string());
    }
  }
}

The example uses OkHttp client, but you are free to use any other HTTP client.

Simple and easy ♡!

The JoobyTest takes care of start and stop the application.

Adding the annotation at class-level starts a single application before running tests and stop it after all them. The default port at class level is: 8911.

Adding the annotation at method-level starts an application before running the test and stop it once it finish. The default port at method level is random.

Default application port can be configured directly using the port() method:

@JoobyTest(value = App.class, port = 9999)

If port is set to zero(0) a random port is selected. You can inject the server port in your test like:

Server port injection
@JoobyTest(App.class)
public void test(int serverPort) {

}

@JoobyTest(App.class)
public void anotherTest(int serverPort) {

}

The parameter name must be serverPort and be of type int. This injection let you access to the random port used for the method-level application test.

There is a serverPath value too, which is the entire path to the server:

Server path injection
@JoobyTest(App.class)
public void test(String serverPath) { (1)

}

The serverPath variable contains the entire path: http://localhost:port/contextPath.

Here is the list of available injectable values:

  • int serverPort: Give you the port where the application is listening. This is named type injection (name and type are required).

  • String serverPath: Give you the entire server path where the application is listening. This is named type injection (name and type are required).

  • io.jooby.Environment: Give you access to the application environment. This is a type injection (name no matter).

  • com.typesafe.config.Config: Give you access to the application environment. This is a type injection (name no matter).

  • io.jooby.Jooby: Give you access to the application. This is a type injection (name no matter).

These values can be injected via parameter or instance fields.

The JoobyTest annotation starts the application using the test environment name. You can creates a conf/application.test.conf file to override any other values for testing purpose.

14. Hot Reload

The jooby run tool allows to restart your application on code changes without exiting the JVM.

This feature is also known as hot reload/swap. Makes you feel like coding against a script language where you modify your code and changes are visible immediately.

The tool uses the JBoss Modules library that effectively reload application classes.

For now jooby run is available as Maven and Gradle plugins.

14.1. Usage

1) Add build plugin:

pom.xml
build.gradle
<plugins>
  ...
  <plugin>
    <groupId>io.jooby</groupId>
    <artifactId>jooby-maven-plugin</artifactId>
    <version>2.1.0</version>
  </plugin>
  ...
</plugins>

2) Set main class

pom.xml
build.gradle
<properties>
  <application.class>myapp.App</application.class>
</properties>

3) Run application

Maven
Gradle
mvn jooby:run

14.2. Compilation & Restart

Changing a java or kotlin file triggers a compilation request. Compilation is executed by Maven/Gradle using an incremental build process.

If compilation succeed, application is restarted.

Compilation errors are printed to the console by Maven/Gradle.

Changing a .conf, .properties file triggers just an application restart request. They don’t trigger a compilation request.

Compiler is enabled by default, except for Eclipse users. Plugin checks for .classpath file in project directory, when found plugin compiler is OFF and let Eclipse compiles the code.

14.3. Options

The next example shows all the available options with their default values:

pom.xml
build.gradle
<plugins>
  ...
  <plugin>
    <groupId>io.jooby</groupId>
    <artifactId>jooby-maven-plugin</artifactId>
    <version>2.1.0</version>
    <configuration>
      <mainClass>${application.class}</mainClass>                  (1)
      <restartExtensions>conf,properties,class</restartExtensions> (2)
      <compileExtensions>java,kt</compileExtensions>                  (3)
      <port>8080</port>                                            (4)
    </configuration>
  </plugin>
  ...
</plugins>
1 Application main class
2 Restart extensions. A change on these files trigger a restart request.
3 Source extensions. A change on these files trigger a compilation request, followed by a restart request.
4 Application port

15. Server

Jooby comes with three server implementations:

Server are automatically registered based on their presence on the project classpath.

To use Jetty, add the dependency:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-jetty</artifactId>
  <version>2.1.0</version>
</dependency>

To use Netty, add the dependency:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-netty</artifactId>
  <version>2.1.0</version>
</dependency>

To use Undertow, add the dependency:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-utow</artifactId>
  <version>2.1.0</version>
</dependency>

Only one server dependency must be available on classpath.

15.1. Options

Server options are available via ServerOptions class:

Server Options
Java
Kotlin
{
  setServerOptions(new ServerOptions()
      .setBufferSize(16384)
      .setPort(8080)
      .setIoThreads(16)
      .setWorkerThreads(64)
      .setGzip(false)
      .setSingleLoop(false)
      .setDefaultHeaders(true)
      .setMaxRequestSize(10485760)
  );
}
  • bufferSize: Buffer size used by server for reading/writing data. Default is: 16k.

  • port: Server HTTP port or 0 for random port. Default is: 8080.

  • ioThreads: Number of IO threads used by the server. Used by Netty and Undertow. Default is: Runtime.getRuntime().availableProcessors() * 2

  • workerThreads: Number of worker (a.k.a application) threads. Default is: ioThreads * 8.

  • gzip: Gzip support. Default is: false.

  • singleLoop: Indicates if the web server should use a single loop/group for doing IO or not. Netty only.

  • defaultHeaders: Configure server to set the following headers: Date, Content-Type and Server headers.

  • maxRequestSize: Maximum request size in bytes. Request exceeding this value results in 413(REQUEST_ENTITY_TOO_LARGE) response. Default is 10mb.

Server options are available as application configuration properties too:

application.conf
server.bufferSize = 16384
server.port = 8080
server.ioThreads = 16
server.workerThreads = 64
server.gzip = false
server.singleLoop = false
server.defaultHeaders = true
server.maxRequestSize = 10485760

16. Extensions and Services

Jooby comes with a simple extension mechanism. The Extension API allows to configure , extend an application by adding shared/single services, infrastructure/technical concerns like dependency injection, database connection pools, cron-job, etc.

Services are shared/singleton objects, usually with a clear lifecycle for starting and stopping them.

16.1. Writing Custom Extension

We are going to develop a custom extension that configure a DataSource service.

Java
Kotlin
import io.jooby.Extension;

public class MyExtension implements Extension {

   public void install(Jooby application) {
      DataSource dataSource = createDataSource();           (1)

      ServiceRegistry registry = application.getServices(); (2)
      registry.put(DataSource.class, dataSource);           (3)

      application.onStop(dataSource::close)                 (4)
   }
}
1 Create the service
2 Access to service registry
3 Add it to the service registry
4 Close/release service on application stop

Let’s install the extension and use the service!!

Java
Kotlin
public class App extends Jooby {

   {
     install(new MyExtension());                      (1)

     get("/", ctx -> {
       MyDataSource ds = require(MyDataSource.class); (2)
       // ...
     });
   }
}
1 Install the extension
2 Use the service

Services are accessible via require(Class).

In addition to services, an extension module may provides infrastructure routes, body decoder/encoder, template engines, etc.

The extension mechanism is a simple way of reusing code and decoupling technical features from business logic.

More advanced techniques are describe in the Dependency Injection section.

17. Dependency Injection

In Jooby 2.x there is no dependency injection framework. This is another major difference with 1.x which was built with Guice.

This make Jooby 2.x more lightweight than 1.x but most important give you freedom of using the dependency injection of your choice.

17.1. Dagger

1) Add Dagger to your project

Maven
Gradle
<dependency>
  <groupId>com.google.dagger</groupId>
  <artifactId>dagger</artifactId>
  <version>2.20</version>
</dependency>

2) Configure annotation processor

Maven
Gradle
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>...</version>
      <configuration>
        <annotationProcessorPaths>
          <path>
            <groupId>com.google.dagger</groupId>
            <artifactId>dagger-compiler</artifactId>
            <version>2.20</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

3) Bootstrap Dagger from application:

Dagger
Java
Kotlin
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
     /** Dagger: */
     AppComponent dagger = DaggerAppComponent.builder()      (1)
         .build();

     get("/", ctx -> {
       MyService service = dagger.getMyService();            (2)
       return service.doSomething();
     });
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Bootstrap dagger component
2 Use dagger provided objects

17.1.1. MVC routes

Integration of MVC routes with Dagger is as simple as:

MVC and Dagger
Java
Kotlin
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    /** Dagger: */
    AppComponent dagger = DaggerAppComponent.builder()  (1)
        .build();

    mvc(dagger.myController());                         (2)
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Bootstrap dagger component
2 Register MVC route provided by Dagger

Due the static nature of Dagger mvc integration identical to normal usage. For custom scopes/lifecycles Dagger generate a javax.inject.Provider on such use cases you need to switch and use the provider version of the mvc method:

MVC and Dagger provider
Java
Kotlin
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    /** Dagger: */
    AppComponent dagger = DaggerAppComponent.builder()      (1)
        .build();

    mvc(MyController.class, dagger.myController());         (2)
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Bootstrap dagger component
2 Register MVC route using a Dagger provider

17.2. Guice

1) Add Guice dependency to your project:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-guice</artifactId>
  <version>2.1.0</version>
</dependency>

2) Install Guice:

Installing Guice
Java
Kotlin
import io.jooby.di.GuiceModule;
import io.jooby.runApp;

public class App extends Jooby {

  {
    install(new GuiceModule());                     (1)

    get("/", ctx -> {
      MyService service = require(MyService.class); (2)
      return service.doSomething();
    });
}

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Install Guice module
2 The require(Class) call is now resolved by Guice

17.2.1. Property Injection

Configuration properties can be injected using the @Named annotation:

application.conf
currency = USD
Java
Kotlin
import javax.injext.Named;
import javax.injext.Inject;

public class BillingService {

  @Inject
  public BillingService(@Named("currency") String currency) {
    ...
  }

}

17.2.2. MVC routes

Guice will also provisioning MVC routes

MVC and Guice
Java
Kotlin
import io.jooby.di.GuiceModule;
import io.jooby.runApp

public class App extends Jooby {

  {
    install(new GuiceModule());  (1)

    mvc(MyController.class);     (2)
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Install Guice module
2 Register a MVC route

The lifecycle of MyController is now managed by Guice. Also:

  • In Guice, the default scope is prototype (creates a new instance per request)

  • If you prefer a single instance add the javax.inject.Singleton annotation

17.3. Spring

1) Add Spring dependency to your project:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-spring</artifactId>
  <version>2.1.0</version>
</dependency>

2) Install Spring:

Installing Spring
Java
Kotlin
package myapp;                                       (1)

import static io.jooby.Jooby.runApp;
import io.jooby.di.SpringModule;

public class App extends Jooby {

  {
    install(new SpringModule());                           (2)

    get("/", ctx -> {
      MyService service = require(MyService.class);  (3)
      return service.doSomething();
    });
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Spring scan the package myapp and subpackages
2 Install Spring module
3 The require(Class) call is now resolved by Spring

Spring uses the application package and sub-packages to scan. If you need extra packages, provide them at creation time:

install(new Spring("foo", "bar"));

17.3.1. Property Injection

Configuration properties can be injected using the @Value annotation:

application.conf
currency = USD
Java
Kotlin
import javax.injext.Inject;
import org.springframework.beans.factory.annotation.Value;

public class BillingService {

  @Inject
  public BillingService(@Value("currency") String currency) {
    ...
  }

}

17.3.2. MVC routes

The Spring extension does a bit more in relation to MVC routes:

  • A MVC route annotated with the org.springframework.stereotype.Controller annotation is automatically registered. No need to register it manually

  • A MVC route provided by Spring is a singleton object by default. Singleton is the default scope in Spring

MVC route
Java
Kotlin
import org.springframework.stereotype.Controller;

@Controller
public class Hello {

   @GET
   public String sayHi() {
     return "Hi Spring!";
   }
}

17.4. Weld

1) Add Weld dependency to your project:

Maven
Gradle
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-weld</artifactId>
  <version>2.1.0</version>
</dependency>

2) Install Weld:

Installing Weld
Java
Kotlin
import io.jooby.di.WeldModule;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    install(new WeldModule());                      (1)

    get ("/", ctx -> {
      MyService service = require(MyService.class); (2)
      service.doSomething();
    });
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
1 Install Weld
2 The require(Class) call is now resolved by Weld

17.4.1. Property Injection

Configuration properties can be injected using the @Named annotation:

application.conf
currency = USD
Java
Kotlin
import javax.injext.Inject;
import javax.injext.Named;

public class BillingService {

  @Inject
  public BillingService(@Named("currency") String currency) {
    ...
  }

}

17.4.2. MVC routes

The Weld extension does a bit more in relation to MVC routes:

  • A MVC route annotated with the Path annotation is automatically registered. No need to register it manually

  • The default scope is prototype (creates a new instance per request). If you prefer a single instance add the javax.inject.Singleton annotation

MVC route
Java
Kotlin
import io.jooby.annotations.*;

@Path("/")
public class Hello {

   @GET
   public String sayHi() {
     return "Hi Weld!";
   }
}

18. Modules

Modules are a key concept for building reusable and configurable pieces of software.

Modules (unlike in other frameworks) are thin and do a lot of work to bootstrap and configure an external library, but they DO NOT provide a new level of abstraction nor [do] they provide a custom API to access functionality in that library. Instead they expose the library components as they are.

Modules are distributed as separated jar/dependency and usually implement the Extension API.

In general they provide a builder class to create the and configure the external library from configuration properties.

Available modules are listed next.

18.1. Data

  • Flyway: Flyway migration module.

  • HikariCP: A high-performance JDBC connection pool.

  • Hibernate: Hibernate ORM module.

  • Jdbi: Jdbi module.

18.2. JSON

  • Jackson: Jackson module for Jooby.

18.3. Template Engine

  • Handlebars: Handlebars templates for Jooby.

  • Freemarker: Freemarker templates for Jooby.

  • Pebble: Pebble templates for Jooby.

  • Rocker: Rocker templates for Jooby.

.

19. Appendix

19.1. Upgrading from 1.x

You will find here notes/tips about how to migrate from 1.x to 2.x.

This is a work in progress document, if something is wrong or missing please report to Github or better edit this file and fix it

19.1.1. API

API still similar/equivalent in 2.x. Except for the one listed below:

Table 1. Classes

1.x

2.x

org.jooby.Module

io.jooby.Extension

org.jooby.Env

io.jooby.Environment

org.jooby.Mutant

io.jooby.Value

org.jooby.Render

io.jooby.MessageEncoder

org.jooby.Parser

io.jooby.MessageDecoder

org.jooby.Err

io.jooby.StatusCodeException

org.jooby.Results

- (removed)

org.jooby.Result

- (removed)

19.1.2. Route Pipeline

The concept of route pipeline still applies for 2.x but works different.

In 1.x there is no difference between handler and filter (including before and after). The way to chain multiple handler/filter was like:

Pipeline in 1.x
{
  use("*", (req, rsp, chain) -> {
    System.out.println("first");
    // Moves execution to next handler: second
    chain.next(req, rsp);
  });

  use("*", (req, rsp, chain) -> {
    System.out.println("second");
    // Moves execution to next handler: third
    chain.next(req, rsp);
  });

  get("/handler", req -> {
    return "third";
  });
}

A filter in 1.x requires a path pattern, here we use a wide matcher * for first and second filters. Both of this filters are going to be executed before the real handler.

Pipeline in 2.x
{
   decorator(next -> ctx -> {
     System.out.println("first");
     // Moves execution to next handler: second
     return next.apply(ctx);
   });

   decorator(next -> ctx -> {
     System.out.println("second");
     // Moves execution to next handler: third
     return next.apply(ctx);
   });

   get("/handler", ctx -> {
     return "third";
   });
}

Execution is identical to 1.x. The first and second decorators are executed before the handler. Differences with 1.x are:

  • Route.Decorator doesn’t support a path pattern. In 1.x the path pattern is required for a filter.

  • Only the handler supports a path pattern and HTTP-method.

  • A handler might have zero or more decorator.

  • In 2.x we chain all the decorator defined before the handler.

The routing matching algorithm in 2.x is more efficient and fast, because:

  • Matches a single path pattern (due decorator lacks of path pattern)

  • Uses a radix tree, not regular expression like in 1.x

  • It never executes a decorator if there isn’t a matching handler

More detailed explanation of route pipeline is available in the router pipeline documentation.