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 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.