1. Overview
Bootique is a very lightweight open-source container-less JVM framework aimed to build next-generation scalable micro-services. It’s built on top of embedded Jetty server and fully supports REST handlers with jax-rs.
In this article, we’ll show how to build a simple web application using Bootique.
2. Maven Dependencies
Let’s start to use Bootique by adding the following dependency into the pom.xml:
<dependency>
<groupId>io.bootique.jersey</groupId>
<artifactId>bootique-jersey</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.bootique</groupId>
<artifactId>bootique-test</artifactId>
<scope>test</scope>
</dependency>
However, Bootique also requires declaring a few BOM (“Bill of Material”) imports. That’s why following
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.bootique.bom</groupId>
<artifactId>bootique-bom</artifactId>
<version>0.23</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
The latest version of Bootique is available in Central Maven Repository.
To build a runnable jar, Bootique relies on maven-shade-plugin. That’s why we also need to add below configuration as well:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
</plugins>
</build>
3. Starting an Application
The simplest way to start a Bootique application is to invoke *Bootique*‘s exec() method from the main method:
public class App {
public static void main(String[] args) {
Bootique.app(args)
.autoLoadModules()
.exec();
}
}
However, this won’t start the embedded server. Once, the above code is run, following log should be displayed:
NAME
com.baeldung.bootique.App
OPTIONS
-c yaml_location, --config=yaml_location
Specifies YAML config location, which can be a file path
or a URL.
-h, --help
Prints this message.
-H, --help-config
Prints information about application modules and their
configuration options.
-s, --server
Starts Jetty server.
These are nothing but the available program arguments that comes pre-bundled with Bootique.
The names are self-explanatory; hence, to start the server we need to pass either –s or –server argument and the server will be up and running on the default port 8080.
4. Modules
Bootique applications are made with collections of “modules”. In Bootique‘s term “A module is a Java library that contains some code” which means it treats every service as a module. It uses Google Guice for Dependency Injection.
To see how it works, let’s create one interface:
public interface HelloService {
boolean save();
}
Now, we need to create an implementation:
public class HelloServiceImpl implements HelloService {
@Override
public boolean save() {
return true;
}
}
There are two ways, in which we can load the module. The first one is to use Guice‘s Module interface, and the other one is by using Bootique‘s BQModuleProvider which is also known as auto-loading.
4.1. Guice Module
Here, we can use Guice‘s Module interface to bind instances:
public class ModuleBinder implements Module {
@Override
public void configure(Binder binder) {
binder
.bind(HelloService.class)
.to(HelloServiceImpl.class);
}
}
Once the module is defined, we need to map this custom module to the Bootique instance:
Bootique
.app(args)
.module(module)
.module(ModuleBinder.class)
.autoLoadModules()
.exec();
4.2. BQModuleProvider (auto-loading)
Here, all we need to do is to define the earlier created module-binder with BQModuleProvider:
public class ModuleProvider implements BQModuleProvider {
@Override
public Module module() {
return new ModuleBinder();
}
}
The advantage of this technique is that we don’t need to map any module information with the Bootique instance.
We just need to create a file in /resources/META-INF/services/io.bootique.BQModuleProvider and write the full name of the ModuleProvider including package name and Bootique will take care of the rest:
com.baeldung.bootique.module.ModuleProvider
Now, we can use @Inject annotation to use the service instances at the runtime:
@Inject
HelloService helloService;
One important thing to note here is that since we’re using Bootique‘s own DI mechanism, we don’t need to use Guice @ImplementedBy annotation for binding the service instances.
5. REST Endpoint
It’s straightforward to create REST endpoints using JAX-RS API:
@Path("/")
public class IndexController {
@GET
public String index() {
return "Hello, baeldung!";
}
@POST
public String save() {
return "Data Saved!";
}
}
To map the endpoints into the Bootique‘s own Jersey instance, we need to define a JerseyModule:
Module module = binder -> JerseyModule
.extend(binder)
.addResource(IndexController.class);
6. Configuration
We can provide inbuilt or custom configuration information in a YAML-based property file.
For example, if we want to start the application on a custom port and add a default URI context ‘hello’, we can use following YAML configuration:
jetty:
context: /hello
connector:
port: 10001
Now, while starting the application, we need to provide this file’s location in the config parameter:
--config=/home/baeldung/bootique/config.yml
7. Logging
Out-of-the-box Bootique comes with a bootique-logback module. To use this module, we need to add the following dependency in the pom.xml:
<dependency>
<groupId>io.bootique.logback</groupId>
<artifactId>bootique-logback</artifactId>
</dependency>
This module comes with a BootLogger interface with we can override to implement custom logging:
Bootique.app(args)
.module(module)
.module(ModuleBinder.class)
.bootLogger( new BootLogger() {
@Override
public void trace( Supplier<String> args ) {
// ...
}
@Override
public void stdout( String args ) {
// ...
}
@Override
public void stderr( String args, Throwable thw ) {
// ...
}
@Override
public void stderr( String args ) {
// ...
}
}).autoLoadModules().exec();
Also, we can define logging configuration information in the config.yaml file:
log:
level: warn
appenders:
- type: file
logFormat: '%c{20}: %m%n'
file: /path/to/logging/dir/logger.log
8. Testing
For testing, Bootique comes with the bootique-test module. There are two ways by which we can test a Bootique application.
The first approach is ‘foreground’ approach which makes all test-cases run on the main test thread.
The other one is ‘background’ approach which makes the test-cases run on an isolated thread pool.
The ‘foreground’ environment can be initialized using BQTestFactory:
@Rule
public BQTestFactory bqTestFactory = new BQTestFactory();
The ‘background’ environment can be initialized using BQDaemonTestFactory:
@Rule
public BQDaemonTestFactory bqDaemonTestFactory = new BQDaemonTestFactory();
Once the environment factory is ready we can write simple test-cases to test the services:
@Test
public void givenService_expectBoolen() {
BQRuntime runtime = bqTestFactory
.app("--server").autoLoadModules()
.createRuntime();
HelloService service = runtime.getInstance( HelloService.class );
assertEquals( true, service.save() );
}
9. Conclusion
In this article, we showed how to build an application using Bootique‘s core modules. There are several other Bootique modules available like bootique-jooq, bootique-kotlin, bootique-job, etc. The full list of available modules is available here.
Like always, the full source code is available over on GitHub.