Your first Liberty application
A minimal Jakarta EE 10 + MicroProfile 6.1 service: pom.xml, server.xml, one JAX-RS resource. Built with mvn package, run on Open Liberty.
This module turns the empty VM into something that responds to an HTTP request. You’ll write a Maven project, a single REST resource, and a Liberty server.xml that the container image will use in module 04.
The Liberty mental model
Open Liberty is a Jakarta EE / MicroProfile server whose configuration is driven by a single server.xml. You enable features (jakartaee-10.0, microProfile-6.1, mpHealth-4.0, …) and Liberty wires up exactly those — no more. The build’s job is to:
- Compile your code into a WAR.
- Bundle a
server.xmlthat knows how to host it. - Hand both to a runtime — either a local Liberty installation (via
liberty-maven-plugin) or a container image (module 04).
Project layout
insurance-app/
├── pom.xml
└── src/
└── main/
├── java/com/example/insurance/
│ ├── InsuranceApplication.java
│ └── api/PingResource.java
└── liberty/config/
└── server.xml
The pom.xml
The key parts:
- Packaging is
war. provided-scope dependencies forjakarta.jakartaee-api10 and the MicroProfile aggregator.liberty-maven-plugin3.10.x pulling inopenliberty-kernel24.0.0.12 (or newer).- Explicit pins for
maven-war-plugin(3.4.0+) andmaven-compiler-plugin(3.13.0+) — without these, Maven 3.8 picks 2011-vintage versions that crash on JDK 21.
The <properties> block sets maven.compiler.source and target to 21, which is also the version your Liberty image will run.
The server.xml
<server description="insurance-app">
<featureManager>
<feature>webProfile-10.0</feature>
<feature>microProfile-6.1</feature>
</featureManager>
<variable name="http.port" defaultValue="9080"/>
<variable name="https.port" defaultValue="9443"/>
<httpEndpoint id="defaultHttpEndpoint"
host="*"
httpPort="${http.port}"
httpsPort="${https.port}"/>
<webApplication contextRoot="/" location="insurance-app.war"/>
</server>
Two choices that matter:
webProfile-10.0, notjakartaee-10.0. The full platform profile pulls in EJB / ORB / messaging and demands a user registry — without one, the JVM crashes ~10 seconds into startup withCWWKS9660E. Module 04 returns to this.<variable>defaults for the ports. When theliberty-maven-pluginpackages a server it substitutes${http.port}from the pom, but when you copyserver.xmlstraight into a container image (module 04) that substitution doesn’t run. Defaults belong in the file.
The JAX-RS plumbing
InsuranceApplication.java:
package com.example.insurance;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/api")
public class InsuranceApplication extends Application {
}
PingResource.java:
package com.example.insurance.api;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.Map;
@Path("/ping")
@Produces(MediaType.APPLICATION_JSON)
public class PingResource {
@GET
public Map<String, String> ping() {
return Map.of("status", "ok");
}
}
Note jakarta.*, never javax.*. Jakarta EE 9 was the rename; 10 is the version we’re on. If you copy code from older tutorials, the javax.* imports will compile and then fail at runtime with ClassNotFoundException.
Build it
mvn -B package
The first build pulls Maven dependencies and the Liberty kernel; expect 2–4 minutes. Subsequent builds are seconds. The output is target/insurance-app.war — a ~5 KB WAR. The WAR is small because the framework lives in the runtime image, not in the artifact.
Run it (optional, the container is what we’ll actually use)
If you want to verify the app runs without a container, point the Liberty Maven plugin at it:
mvn -B liberty:run
This downloads a full Liberty runtime under target/liberty/, drops your WAR into it, and starts the server. curl http://localhost:9080/api/ping returns {"status":"ok"}. Stop with Ctrl-C.
For development iteration there’s also mvn liberty:dev — hot-reloads code and config without restarting Liberty. We won’t use it in this track because module 04 moves us to containers, but it’s the killer feature when you’re iterating fast on one feature.
Reference snapshot
If you want to compare your pom.xml/server.xml/Java files against a canonical version, the companion insurance-app repo carries the chapter-aligned-scaffold branch — exactly the files this chapter (plus module 04’s Containerfile) produces, and nothing else:
git clone https://github.com/zeshaq/insurance-app.git
cd insurance-app
git checkout chapter-aligned-scaffold
A note for archaeology: the repo’s very first scaffold commit (681234a) on main predates this chapter and ships <feature>jakartaee-10.0</feature> plus no <variable> defaults — both the exact anti-patterns this chapter warns against. The chapter-aligned-scaffold branch is rooted off that commit but applies the two corrections, so it’s the one to verify against. main HEAD itself has Liberty extended for all 14 slices — useful later, less helpful for just modules 03/04.
The first mvn liberty:run on a brand-new VM also downloads the Liberty kernel + every feature from Maven Central — count on ~3 minutes that first time, seconds afterward. mvn package itself stays well under 30 seconds.
What you have
- A Maven project that builds to a tiny WAR (a few KB — the framework lives in the runtime, not the artifact).
- A
server.xmlwith the right feature set for a REST service. - A
/api/pingendpoint, verifiable in seconds withmvn liberty:run.
Module 04 wraps this WAR in a podman image.