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> section needs to be added in the pom.xml:
<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.