1. Introduction
Javalin is a lightweight web framework written for Java and Kotlin. It’s written on top of the Jetty web server, which makes it highly performant. Javalin is modeled closely off of koa.js, which means it’s written from the ground up to be simple to understand and build on.
In this tutorial, we’ll walk through steps of building a basic REST microservice using this light framework.
2. Adding Dependencies
To create a basic application, we only need one dependency – Javalin itself:
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin</artifactId>
<version>1.6.1</version>
</dependency>
The current version can be found here.
3. Setting up Javalin
Javalin makes setting up a basic application easy. We’re going to start by defining our main class and setting up a simple “Hello World” application.
Let’s create a new file in our base package called JavalinApp.java.
Inside this file, we create a main method and add the following to set up a basic application:
Javalin app = Javalin.create()
.port(7000)
.start();
app.get("/hello", ctx -> ctx.html("Hello, Javalin!"));
We’re creating a new instance of Javalin, making it listen on port 7000, and then starting the application.
We’re also setting up our first endpoint listening for a GET request at the /hello endpoint.
Let’s run this application and visit http://localhost:7000/hello to see the results.
4. Creating a UserController
A “Hello World” example is great for introducing a topic, but it isn’t beneficial for a real application. Let’s look into a more realistic use case for Javalin now.
First, we need to create a model of the object we’re working with. We start by creating a package called user under the root project.
Then, we add a new User class:
public class User {
public final int id;
public final String name;
// constructors
}
Also, we need to set up our data access object (DAO). We’ll use an in-memory object to store our users in this example.
We create a new class in the user packaged called UserDao.java:
class UserDao {
private List<User> users = Arrays.asList(
new User(0, "Steve Rogers"),
new User(1, "Tony Stark"),
new User(2, "Carol Danvers")
);
private static UserDao userDao = null;
private UserDao() {
}
static UserDao instance() {
if (userDao == null) {
userDao = new UserDao();
}
return userDao;
}
Optional<User> getUserById(int id) {
return users.stream()
.filter(u -> u.id == id)
.findAny();
}
Iterable<String> getAllUsernames() {
return users.stream()
.map(user -> user.name)
.collect(Collectors.toList());
}
}
Implementing our DAO as a singleton makes it easier to use in the example. We could also declare it as a static member of our main class or use dependency injection from a library like Guice if we wanted to.
Finally, we want to create our controller class. Javalin allows us to be very flexible when we declare our route handlers, so this is only one way of defining them.
We create a new class called UserController.java in the user package:
public class UserController {
public static Handler fetchAllUsernames = ctx -> {
UserDao dao = UserDao.instance();
Iterable<String> allUsers = dao.getAllUsernames();
ctx.json(allUsers);
};
public static Handler fetchById = ctx -> {
int id = Integer.parseInt(Objects.requireNonNull(ctx.param("id")));
UserDao dao = UserDao.instance();
Optional<User> user = dao.getUserById(id);
if (user.isPresent()) {
ctx.json(user);
} else {
ctx.html("Not Found");
}
};
}
By declaring the handlers as static, we ensure that the controller itself holds no state. But, in more complex applications, we may want to store state between requests, in which case we’d need to remove the static modifier.
Also note that unit testing is harder with static methods, so if we want that level of testing we will need to use non-static methods.
5. Adding Routes
We now have multiple ways of fetching data from our model. The last step is to expose this data via REST endpoints. We need to register two new routes in our main application.
Let’s add them to our main application class:
app.get("/users", UserController.fetchAllUsernames);
app.get("/users/:id", UserController.fetchById);
After compiling and running the application, we can make a request to each of these new endpoints. Calling http://localhost:7000/users will list all users and calling http://localhost:7000/users/0 will get the single User JSON object with the id 0. We now have a microservice that allows us to retrieve User data.
6. Extending Routes
Retrieving data is a vital task of most microservices.
However, we also need to be able to store data in our datastore. Javalin provides the full set of path handlers that are required to build services.
We saw an example of GET above, but PATCH, POST, DELETE, and PUT are possible as well.
Also, if we include Jackson as a dependency, we can parse JSON request bodies automatically into our model classes. For instance:
app.post("/") { ctx ->
User user = ctx.bodyAsClass(User.class);
}
would allow us to grab the JSON User object from the request body and translate it into the User model object.
7. Conclusion
We can combine all of these techniques to make our microservice.
In this article, we saw how to set up Javalin and build a simple application. We also talked about how to use the different HTTP method types to let clients interact with our service.
For more advanced examples of how to use Javalin, be sure to check out the documentation.
Also, as always, the code can be found over on GitHub.