Introduction to Jooby: efficient web development with functional Java
Jooby is one of the best microframeworks for developing web applications with Java. Be it a REST API or an MVC application.
Jooby brings great help to develop software more based on functional programming and fluent APIs. Thus we gain simplicity and ease.
Let’s see its fundamentals and create a sample application.
Understanding Jooby
Jooby provides essential features for web application development like routing, session management, WebSocket, etc. Then we can choose between Netty, Jetty, and Undertow to be used as the server.
See an example of a minimal application:
import io.jooby.Jooby;
public class App {
public static void main(String[] args) {
Jooby.runApp(args, app -> {
app.get("/", ctx -> "Hello, world.");
});
}
}
The above example is enough to start the server on default port 8080
and set an endpoint to return "Hello, world.".
It also has modules to integrate frameworks and libraries. For example, the modules for template engines like Thymeleaf and Freemarker to create applications with MVC architecture.
Jooby is quite flexible.
Good reasons to use Jooby
Jooby has good advantages for web application development.
Firstly, its router that works with fluent interface and lambda expressions. No need for reflection and annotations in your controllers.
There is no enforcement to use a dependency injection framework, it can be done manually. This is important, because it brings more simplicity and efficiency. I wrote an article explaining it: Manual dependency injection in Java — why and how to do it.
Jooby doesn’t overcomplicate things by adding unnecessary layers, abstractions, bloating, etc.
Its simplicity makes it easy to learn and a pleasure to develop with.
It has programmatic configuration, reactive programming support, and good documentation.
It is fast and scalable.
The project is mature. Its first stable version was released in 2016.
Creating an application
Create a project called joobysample. This tutorial will use Maven. The link to the final source code is at the end of the article.
Add the project properties in your pom.xml
file:
<properties>
<!-- Startup class -->
<application.class>com.example.joobysample.App</application.class>
<jooby.version>3.0.1</jooby.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.parameters>true</maven.compiler.parameters>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Add the jooby-netty
dependency in your pom.xml
file:
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-netty</artifactId>
</dependency>
Add the Jooby module for Jackson to facilitate JSON handling. It also has modules for other JSON parsers (Gson and JSON-B).
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-jackson</artifactId>
</dependency>
For logging, we can use any implementation of SLF4J, since Jooby already uses it. So add an implementation dependency:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
In the build
element, add the plugins. The jooby-maven-plugin
adds hot reload to the application, so you can start the application with the mvn jooby:run
command and it restarts when a change in code happens.
Also, leave the resources
directories included for future files.
<build>
<resources>
<resource>
<directory>conf</directory>
</resource>
<resource>
<directory>src${file.separator}main${file.separator}resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<!-- jooby:run -->
<plugin>
<groupId>io.jooby</groupId>
<artifactId>jooby-maven-plugin</artifactId>
<version>${jooby.version}</version>
</plugin>
<!-- Build uber jar -->
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>uber-jar</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${application.class}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Finally, add the dependencyManagement
:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-bom</artifactId>
<version>${jooby.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
The configuration is finished and we can start developing.
Create the App
class in the com.example.joobysample
package.
This class will have the entry point of our application:
import io.jooby.Jooby;
public class App {
public static void main(String[] args) {
Jooby.runApp(args, app -> {
app.get("/", ctx -> "Service running.");
});
}
}
Start your application. It should be running on http://localhost:8080/
. Make a request to see the response message.
How the router works
The router has methods with names equivalent to HTTP methods: get, post, put, etc.
The methods take the path in the first parameter, and in the second a functional interface (i.e. lambda expression) that takes a Context
and returns the response.
The Context
has the request information, such as request body, request parameters, query string, etc. And it also has methods to set response information like status code, response headers, etc.
app.post("/employees", (Context ctx) -> {
// Getting the request body:
String requestBody = ctx.body().valueOrNull();
// Changing the response status code:
ctx.setResponseCode(StatusCode.CREATED);
// Returning the response body:
return "Created...";
});
app.put("/employees/{id}", ctx -> {
// Getting a variable from URL:
var id = ctx.path("id").longValue();
return "Updated...";
});
Creating a controller
Let’s organize the routing of our requests in controller classes. This is the way to organize the routes that I like the most. Other ways are also possible though.
The controller will be similar to Spring and JAX-RS controllers in some points:
- Take the dependencies in the constructor and assign them to private attributes.
- Create a method for each endpoint. They take the request information as a parameter and return the response.
But to bind a path to a method, instead of using annotations, we’ll use the fluent router in addRoutes
method and method reference (or lambda expression) for each endpoint.
See how it will be:
package com.example.joobysample;
import io.jooby.Context;
import io.jooby.Jooby;
import java.util.List;
public class ProductController {
private final ProductRepository productRepository;
public ProductController(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public void addRoutes(Jooby app) {
app.get("/products", this::getNewArrivals);
}
private List<Product> getNewArrivals(Context ctx) {
return productRepository.findNewArrivals();
}
}
To simplify our example, we’ll use a mock ProductRepository
. We won't have to deal with database configuration now, just use an unmodifiable List
. By the way, to query relational databases I recommend jOOQ which is excellent and based on fluent API and code generation.
Create the ProductRepository
class with the selectAll
method:
package com.example.joobysample;
import java.util.List;
public class ProductRepository {
private static final List<Product> products = List.of(
new Product(1L, "product1"),
new Product(2L, "product2"));
public List<Product> findNewArrivals() {
return products;
}
}
Create the Product
class:
package com.example.joobysample;
public record Product(Long id, String name) { }
Finishing the App class
In the main
method we instantiate, configure, and assemble our objects. Since we don't use a dependency injection framework, we need to instantiate manually.
After that, we call the static method runApp
of the Jooby
class. We pass a lambda expression as the second parameter where we call the addRoutes
method of our controllers and install the Jackson module.
package com.example.joobysample;
import io.jooby.Jooby;
import io.jooby.jackson.JacksonModule;
public class App {
public static void main(final String[] args) {
var productRepository = new ProductRepository();
var productController = new ProductController(productRepository);
Jooby.runApp(args, app -> {
app.install(new JacksonModule());
app.get("/", ctx -> "Service running.");
productController.addRoutes(app);
});
}
}
So this is how we would structure our application in real world scenarios.
Start the application and make a request to http://localhost:8080/products
to get the product list. And you should get a response like this:
[{"id":1,"name":"product1"},{"id":2,"name":"product2"}]
Conclusion
In this article, we learned the advantages and basic features of Jooby.
The sample source code is on GitHub: .
Jooby documentation: .