A minimal, production-style Open Liberty application built with Gradle that exposes MicroProfile Health and MicroProfile Metrics endpoints.
- Exposes
/health,/health/live, and/health/readyvia MicroProfile Health 4.0 - Exposes
/metricsin Prometheus format via MicroProfile Metrics 5.0 - Runs on Open Liberty — a lightweight, modular Jakarta EE application server by IBM
- Built and managed entirely with Gradle using the Liberty Gradle Plugin
| Technology | Version | Purpose |
|---|---|---|
| Java | 21 (OpenJDK) | Runtime language |
| Open Liberty | 26.x (auto-downloaded) | Application server |
| Gradle | 8.8 (via wrapper) | Build tool |
| Liberty Gradle Plugin | 3.9.5 | Manages Liberty lifecycle from Gradle |
| MicroProfile Health | 4.0 | /health endpoints |
| MicroProfile Metrics | 5.0 | /metrics Prometheus endpoint |
| Jakarta EE | 10.0 | Platform APIs |
| CDI | 4.0 | Dependency injection for health check classes |
openliberty-microprofile-metrics-gradle/
├── build.gradle # Gradle build config — dependencies, Liberty plugin
├── settings.gradle # Project name declaration
├── gradlew # Gradle wrapper script (always use this, not system gradle)
├── gradlew.bat # Windows wrapper (ignore on Linux)
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar # Wrapper bootstrapper
│ └── gradle-wrapper.properties # Pins Gradle to version 8.8
└── src/
└── main/
├── java/
│ └── com/poc/health/
│ ├── LivenessCheck.java # @Liveness — is the app alive?
│ └── ReadinessCheck.java # @Readiness — is the app ready for traffic?
├── liberty/
│ └── config/
│ └── server.xml # Liberty config: features, ports, security
└── webapp/
└── WEB-INF/
└── beans.xml # Activates CDI so Java annotations work
| Requirement | Version | Check command |
|---|---|---|
| Java JDK | 21 | java -version |
| Gradle (optional) | any | Only needed to generate wrapper — wrapper handles the rest |
| Internet access | — | First run downloads Liberty + dependencies |
Install Java 21 on Ubuntu if not present:
sudo apt update
sudo apt install -y openjdk-21-jdk
java -versionYou do not need Gradle installed globally. The ./gradlew wrapper script downloads and uses Gradle 8.8 automatically on first run.
git clone https://github.com/YOUR_USERNAME/openliberty-microprofile-metrics-gradle.git
cd openliberty-microprofile-metrics-gradle
# First run downloads: Gradle 8.8, Open Liberty runtime, all dependencies
# Takes 3-5 minutes on first run, fast after that
./gradlew libertyRunWait for this line in the output:
[AUDIT] CWWKF0011I: The server pocServer is ready to run a smarter planet.
Open a second terminal in the same directory:
# Overall health — aggregates all checks
curl http://localhost:9080/health
# Liveness check only
curl http://localhost:9080/health/live
# Readiness check only
curl http://localhost:9080/health/ready
# All metrics in Prometheus format (no auth required on HTTP)
curl http://localhost:9080/metrics
# JVM thread count specifically
curl http://localhost:9080/metrics | grep thread/health
{
"status": "UP",
"checks": [
{
"name": "poc-liveness",
"status": "UP",
"data": {}
},
{
"name": "poc-readiness",
"status": "UP",
"data": {
"instance": "B-canary",
"version": "1.0"
}
}
]
}/metrics | grep thread (Prometheus format)
thread_count{mp_scope="base",} 35.0
thread_daemon_count{mp_scope="base",} 32.0
thread_max_count{mp_scope="base",} 42.0
threadpool_activeThreads{mp_scope="vendor",pool="Default_Executor",} 1.0
threadpool_size{mp_scope="vendor",pool="Default_Executor",} 4.0
# Foreground — see all logs live, Ctrl+C to stop
./gradlew libertyRun
# Background — Liberty runs as a daemon process
./gradlew libertyStart
# Stop a background Liberty instance
./gradlew libertyStop
# Compile and build WAR only — does not start Liberty
./gradlew build
# Build WAR, skipping tests
./gradlew build -x test
# Delete all build artifacts and start fresh
./gradlew clean
# Dev mode — auto-reloads on file save (like nodemon for Java)
./gradlew libertyDevThe Gradle equivalent of Maven's pom.xml. Defines:
- The Liberty Gradle Plugin (downloads and manages Liberty)
providedCompiledependencies — APIs needed to compile but NOT bundled in the WAR (Liberty provides them at runtime)- The WAR filename — must match
server.xml's<webApplication location="..."/>
Liberty's main configuration file. Controls:
- Which features are loaded (
cdi-4.0,mpHealth-4.0,mpMetrics-5.0) - HTTP/HTTPS ports (9080 / 9443)
- Security config for the
/metricsendpoint <mpMetrics authentication="false"/>— allows Prometheus to scrape/metricsover HTTP without credentials (appropriate for internal network POC)
Required by CDI (Jakarta Contexts and Dependency Injection). Without this file, Liberty ignores your @Liveness and @Readiness annotated classes entirely. The bean-discovery-mode="all" setting tells CDI to scan all classes in the WAR.
Implements @Liveness. Answers: "Is the app alive, or should the server restart it?"
In Kubernetes, if liveness returns DOWN, the pod is killed and restarted. For this POC it always returns UP. In production you'd check things like: is the JVM out of memory? Are threads deadlocked?
Implements @Readiness. Answers: "Is the app ready to receive traffic?"
In Kubernetes, if readiness returns DOWN, the pod is temporarily removed from the load balancer rotation but not killed. Use this to signal: "I'm starting up" or "I'm in maintenance mode". This check returns instance metadata (instance: B-canary) which is useful for verifying canary routing.
# Build the WAR locally
./gradlew build -x test
# Copy WAR to remote server
scp build/libs/liberty-poc.war ubuntu@YOUR_SERVER_IP:/tmp/
# On the remote server — drop into Liberty's dropins folder for auto-deploy
ssh ubuntu@YOUR_SERVER_IP \
"sudo cp /tmp/liberty-poc.war \
~/openliberty-microprofile-metrics-gradle/build/wlp/usr/servers/pocServer/dropins/"Liberty auto-detects and deploys any WAR placed in the dropins/ directory — no restart needed.
Add this to your prometheus.yml to scrape this instance:
scrape_configs:
- job_name: 'liberty-canary'
static_configs:
- targets: ['YOUR_VM3_IP:9080']
labels:
instance: 'VM3'
version: 'canary'
build: 'gradle'| Metric name | Type | Description |
|---|---|---|
thread_count |
gauge | Current JVM live thread count |
thread_daemon_count |
gauge | Current daemon thread count |
thread_max_count |
gauge | Peak thread count since JVM start |
threadpool_activeThreads |
gauge | Liberty executor threads currently working |
threadpool_size |
gauge | Total Liberty thread pool size |
memory_committedHeap_bytes |
gauge | JVM committed heap in bytes |
cpu_processCpuLoad_percent |
gauge | JVM CPU utilization |
jvm_uptime_seconds |
gauge | Seconds since Liberty started |
Grafana query to show thread count across both Liberty instances side by side:
thread_count{mp_scope="base"}
MicroProfile Health — a specification for Java microservices to expose standardised health check endpoints. Used by Kubernetes liveness and readiness probes to manage pod lifecycle automatically.
MicroProfile Metrics — exposes JVM and application metrics in Prometheus text format. The /metrics endpoint is scraped by Prometheus and visualised in Grafana.
Liberty Gradle Plugin — IBM's official Gradle plugin that manages the full Liberty server lifecycle: download, configure, start, deploy, stop. Equivalent to the liberty-maven-plugin for Maven projects.
Gradle Wrapper (./gradlew) — pins the Gradle version inside the project so every developer and CI pipeline uses the exact same version regardless of what's installed on the machine. Industry standard practice — always commit the wrapper, always use ./gradlew not gradle.
WAR file — Web Application Archive. The standard Java deployment artifact for web applications. Contains your compiled classes, dependencies, and configuration. Dropped into an application server (Liberty, Tomcat, etc.) to run.
CDI (Contexts and Dependency Injection) — Jakarta EE's dependency injection framework. The @Liveness, @Readiness, and @ApplicationScoped annotations only work because CDI is active. beans.xml is what activates it.
gradle: command not found when generating wrapper
# Install via SDKMAN
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install gradle 8.8./gradlew: Permission denied
chmod +x gradlewPort 9080 already in use
# Find what's using it
ss -tlnp | grep 9080
# Kill it or change the port in server.xmlJAVA_HOME is not set
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' >> ~/.bashrc
source ~/.bashrc/metrics returns empty on HTTP
Make sure server.xml contains:
<mpMetrics authentication="false"/>Without this, Liberty only serves /metrics over HTTPS (port 9443) with credentials.
Liberty not picking up code changes
Use ./gradlew libertyDev instead of libertyRun. Dev mode watches for file changes and hot-reloads automatically.
Example Promtail Config
server:
http_listen_port: 9081
grpc_listen_port: 0
positions:
filename: /root/promtail/positions.yaml
# Where to send logs — Loki on VM1
clients:
- url: http://192.168.77.90:3100/loki/api/v1/push
scrape_configs:
- job_name: 'liberty-logs-vm3'
static_configs:
- targets:
- localhost
labels:
job: 'liberty'
instance: 'VM3'
version: 'canary'
# __path__ tells Promtail which files to read
__path__: /root/openliberty-microprofile-metrics-gradle/build/wlp/usr/servers/pocServer/logs/*.logBuilt as a DevOps POC demonstrating Open Liberty, MicroProfile observability, Gradle build tooling, and NGINX canary deployments on an on-premise 3-VM cluster.