1. Hello World in Bootique
The goal of this chapter is to write a simple REST app using Bootique. Let’s start with a new Java Maven project created in your favorite IDE. Your pom.xml in addition to the required project information tags will need to declare a few BOM ("Bill of Material") imports in the <dependencyManagement/> section:
Maven
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.bootique.bom</groupId>
<artifactId>bootique-bom</artifactId>
<version>4.0-M3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Gradle
dependencies {
implementation platform("io.bootique.bom:bootique-bom:4.0-M3")
}
This will allow <dependencies/> section that will follow shortly to include various Bootique modules and not worry about their individual versions. So let’s create this section and import a few modules:
Maven
<dependencies>
<dependency>
<groupId>io.bootique.jersey</groupId>
<artifactId>bootique-jersey</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.bootique.logback</groupId>
<artifactId>bootique-logback</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
Gradle
dependencies {
implementation 'io.bootique.jersey:bootique-jersey'
implementation 'io.bootique.logback:bootique-logback'
}
As you see we want a bootique-jersey and bootique-logback modules in our app. Those may depend on other modules, but we don’t have to think about it. Those dependencies will be included by Maven automatically. Now let’s create the main Java class that will run the app:
import io.bootique.Bootique;
public class Application {
public static void main(String[] args) {
Bootique
.app(args)
.autoLoadModules()
.exec()
.exit();
}
}
There’s only one line of meaningful code inside the main() method, but this is already a working Bootique app. Meaning it is runnable and can do a few things. So let’s try running this class from your IDE. You will see the output like this on the IDE console:
NAME
com.foo.Application
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.
So the app printed its help message telling us which commands and options it understands. --server option looks promising, but before we use it, let’s actually write a REST endpoint that will answer to our requests. We’ll use Jakarta JAX-RS API for that:
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/")
public class HelloResource {
@GET
public String hello() {
return "Hello, world!";
}
}
Note that we could have placed this code straight in the Application class. Which makes for an effective demo (an app that can fit in one class), but not for a particularly clean design. So keeping the resource in its own class. Now let’s amend the Application to tell Bootique where to find the resource:
import io.bootique.Bootique;
import io.bootique.BQModule;
import io.bootique.di.Binder;
public class Application {
public static void main(String[] args) {
BQModule module = binder ->
JerseyModule.extend(binder).addApiResource(HelloResource.class); (1)
Bootique
.app(args)
.module(module) (2)
.autoLoadModules()
.exec()
.exit();
}
}
| 1 | Here we created our own module that registers a resource with the JerseyModule. |
| 2 | Here we registered that "inline" module with Bootique |
Now let’s try to run the app with the changes. Add --server to the command run parameters before doing it. Now when the app is started, you will see a different output:
INFO main i.b.j.s.ServletFactory: Adding servlet 'jersey' mapped to /*
INFO main i.b.j.s.ServerLifecycleLogger - Starting jetty...
INFO main o.e.j.s.Server - jetty-12.1.7
...
INFO main o.e.j.s.AbstractConnector - Started oejs.ServerConnector@5bcb04cb{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
INFO main o.e.j.s.Server - Started oejs.Server@56ac5c80{STARTING}[12.1.7,sto=0] @26ms
INFO main i.b.j.s.ServerLifecycleLogger - Started Jetty in 29 ms. Base URL: http://127.0.0.1:8080
Notice that the app did not terminate immediately, and is waiting for user requests. Now let’s try opening the URL http://localhost:8080/ in the browser. We should see 'Hello, world!' as request output. We just built a working REST app that does not require deployment to a web container, and generally wasn’t that hard to write. The takeaway here is this:
-
You start the app via
Bootiqueclass, that gives you a runnable "shell" of your future app in one line of code. -
Declaring modules in the app dependencies and using
Bootique.autoLoadModules()gives the app the ability to respond to commands from those modules (in our exampl,--servercommand coming from implicitbootique-jettystarted an embedded web server ). -
You can contribute your own code to modules to build an app with desired behavior.
Next, we’ll talk about configuration…
2. Configuration
You can optionally pass a configuration to almost any Bootique app. This is done with a --config parameter. An argument to --config is either a path to a configuration file or a URL of a service that serves such configuration remotely (imagine an app starting on a cloud that downloads its configuration from a central server). The format of the file is YAML (though, just like everything in Bootique, this can be customized). Let’s create a config file that changes Jetty listen port and the app context path. To do this create a file in the app run directory, with an arbitrary name, e.g. myconfig.yml with the following contents:
jetty:
context: /hello
connectors:
- port: 10001
Now restart the app with the new set of parameters: --server --config=myconfig.yml. After the restart the app would no longer respond at http://localhost:8080/, instead you will need to use a new URL: http://localhost:10001/hello. This is just a taste of what can be done with configuration. Your app can just as easily obtain its own specific configuration in a form of an app-specific object, as described elsewhere in the docs.
3. Injection
We’ve mentioned that Bootique is built on own Bootique dependency injection (DI) container. We’ll talk more about injection elsewhere. Here we’ll provide a simple example. Our simple app already has a number of objects and services working behind the scenes that can be injected. One of them is command-line arguments that were provided to the app on startup. Let’s extend our resource to include those arguments in the output:
import io.bootique.annotation.Args;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/")
public class HelloResource {
@Inject
@Args
private String[] args;
@GET
public String hello() {
String allArgs = String.join(" ", args);
return "Hello, world! The app was started with these args: " + allArgs;
}
}
As you see, we declared a variable of type String[] and annotated it with @jakarta.inject.Inject and @io.bootique.annotation.Args. @Inject ensures that the value is initialized via injection, and @Args tells Bootique which one of possibly many String[] instances from the DI container we are expecting here.
Now restart the app and refresh http://localhost:10001/hello in the browser. The new output will be "Hello, world! The app was started with the following arguments: `--server --config=myconfig.yml`".
Next, let’s build and run the app outside the IDE…
4. Packaging
Till now, we’ve been running our app from IDE. Now let’s package it as a runnable "fat" jar to be able to start it from command line (and potentially deploy to a remote server). This requires a bit of configuration of the Maven maven-shade-plugin:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${main.class}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
</plugins>
</build>
Now you should set the name of the main class as property:
<properties>
<main.class>io.bootique.getstarted.step2.Application</main.class>
</properties>
Once this is done, you can build and run the app:
mvn clean package
# Using myapp-1.0.jar as an example; the actual jar name depends on your POM settings
java -jar target/myapp-1.0.jar --server --config=myconfig.yml
The result should be the same as running from the IDE, and the app should be still accessible at http://localhost:10001/hello. But now your jar can also be deployed in any Java environment.
This concludes our simple tutorial. You can further explore our documentation here.