Uptime Monitoring for Quarkus Applications
Your Quarkus application is running in production — but is it actually healthy? Quarkus ships with MicroProfile Health built-in, which is great, but a local health endpoint is only half the picture. You need an independent external observer that probes your app from outside your infrastructure and alerts you the moment something goes wrong.
One silent 503 at 2 AM, a scheduled CDI task that stopped running after a bad deploy, a datasource connection pool that quietly exhausted itself — these failures cost you users before your internal dashboards catch them. By the end of this guide you'll have external uptime monitoring, multi-region checks, heartbeat monitoring for your Quarkus scheduled tasks, and a public status page — all on the free tier.
Why Quarkus apps still go dark
Quarkus apps fail in two distinct ways that internal tooling misses:
Endpoint failures — your REST endpoints start returning 500s or timeouts after a bad deploy or a saturated datasource pool. The JVM process is alive. The Quarkus runtime is responsive. Your /q/health/live liveness probe returns UP while business endpoints are broken.
Silent scheduler failures — a @Scheduled method throws an unchecked exception, Quarkus logs it, and the next invocation fires on schedule with no alert. Your nightly data-sync job has been failing silently for a week.
Both fail invisibly from inside your own infrastructure. You need an external service probing from the outside.
Step 1: Expose a health endpoint with MicroProfile Health
Add the SmallRye Health extension if you don't have it yet:
./mvnw quarkus:add-extension -Dextensions="smallrye-health"
Or with Gradle:
./gradlew addExtension --extensions="smallrye-health"
Quarkus automatically exposes /q/health, /q/health/live, and /q/health/ready once the extension is on the classpath. Add a custom readiness check that validates your database connection:
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;
import javax.sql.DataSource;
import java.sql.Connection;
@Readiness
@ApplicationScoped
public class DatabaseHealthCheck implements HealthCheck {
private final DataSource dataSource;
public DatabaseHealthCheck(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public HealthCheckResponse call() {
try (Connection connection = dataSource.getConnection()) {
boolean valid = connection.isValid(2);
return HealthCheckResponse.named("database")
.status(valid)
.withData("url", connection.getMetaData().getURL())
.build();
} catch (Exception e) {
return HealthCheckResponse.named("database")
.down()
.withData("error", e.getMessage())
.build();
}
}
}
Quarkus aggregates all registered HealthCheck beans automatically. When the database check fails the /q/health/ready endpoint returns HTTP 503 — exactly the signal your monitoring tool needs.
Verify locally:
curl -s http://localhost:8080/q/health/ready | jq .
Expected output when healthy:
{
"status": "UP",
"checks": [
{
"name": "database",
"status": "UP",
"data": {
"url": "jdbc:postgresql://localhost:5432/mydb"
}
}
]
}
Step 2: Add a custom application health check
For external APIs and caches beyond your primary datasource, implement additional HealthCheck beans:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Readiness
@ApplicationScoped
public class ExternalApiHealthCheck implements HealthCheck {
@Inject
@RestClient
PaymentServiceClient paymentClient;
@Override
public HealthCheckResponse call() {
try {
paymentClient.ping();
return HealthCheckResponse.named("payment-service").up().build();
} catch (Exception e) {
return HealthCheckResponse.named("payment-service")
.down()
.withData("error", e.getMessage())
.build();
}
}
}
If you want a single unified /health path instead of /q/health/ready, configure it in application.properties:
quarkus.smallrye-health.root-path=/health
quarkus.smallrye-health.readiness-path=/health/ready
quarkus.smallrye-health.liveness-path=/health/live
Step 3: Set up external monitoring with Vigilmon
With /q/health/ready returning accurate 503s on failure, point Vigilmon at it:
- Sign up at vigilmon.online — free tier, no credit card
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/q/health/ready - Set check interval (5 minutes on free tier)
- Save
Vigilmon probes from multiple geographic regions simultaneously. A non-2xx response or connection timeout opens an incident and alerts you immediately.
Layer multiple monitors for different application concerns:
| Endpoint | What it catches |
|---|---|
| /q/health/ready | Database down, external API failures, datasource pool exhaustion |
| /q/health/live | JVM hang, deadlock, application crash |
| /api/v1/orders | Business API layer breakage independent of health checks |
| / | Frontend or CDN serving broken |
Step 4: Heartbeat monitoring for @Scheduled tasks
HTTP uptime monitors cannot see inside your application's scheduler. For @Scheduled methods you need heartbeat monitoring.
The pattern: ping a unique URL at the end of each successful job run. If Vigilmon stops receiving pings within the expected window, it fires an alert. No ping equals a failed or crashed job.
Create a CDI bean for the heartbeat client:
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.Optional;
import java.util.logging.Logger;
@ApplicationScoped
public class HeartbeatClient {
private static final Logger log = Logger.getLogger(HeartbeatClient.class.getName());
@ConfigProperty(name = "heartbeat.url")
Optional<String> heartbeatUrl;
public void ping(String jobName) {
if (heartbeatUrl.isEmpty() || heartbeatUrl.get().isBlank()) {
return;
}
try {
java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
.uri(java.net.URI.create(heartbeatUrl.get()))
.GET()
.build();
client.send(request, java.net.http.HttpResponse.BodyHandlers.discarding());
} catch (Exception e) {
// Log but don't throw — monitoring outage must not crash the job
log.warning("Heartbeat ping failed for " + jobName + ": " + e.getMessage());
}
}
}
Wire it into your scheduled task:
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.logging.Logger;
@ApplicationScoped
public class NightlySyncJob {
private static final Logger log = Logger.getLogger(NightlySyncJob.class.getName());
@Inject
SyncService syncService;
@Inject
HeartbeatClient heartbeat;
@Scheduled(cron = "0 0 2 * * ?", identity = "nightly-sync")
void run() {
try {
syncService.syncAll();
// Only ping on success — exception skips the heartbeat
heartbeat.ping("nightly-sync");
} catch (Exception e) {
log.severe("Nightly sync failed: " + e.getMessage());
// Don't ping — let Vigilmon detect the missed beat
}
}
}
Configure the heartbeat URL per environment in application.properties:
# Development — leave blank to disable
heartbeat.url=
# Production (override with env var)
# HEARTBEAT_URL=https://vigilmon.online/ping/your-unique-token
In Vigilmon:
- Click New Monitor → Heartbeat
- Set the expected interval (e.g. 25 hours for a nightly job — 1-hour grace window)
- Copy the unique ping URL
- Set
HEARTBEAT_URLin your production environment
Step 5: Webhook alerts and the uptime badge
Slack and Discord alerts:
- In Vigilmon go to Notifications → New Channel
- Choose Slack or Discord, paste your webhook URL
- Enable it on your monitors
You'll get an instant alert when a monitor goes down and a recovery notification when it comes back up.
Add an uptime badge to your README:
[](https://vigilmon.online?utm_source=devto&utm_medium=article&utm_campaign=quarkus-tutorial)
What you've built
| What | How |
|---|---|
| External health checks | /q/health/ready + Vigilmon HTTP monitor |
| Database readiness check | @Readiness MicroProfile Health bean |
| External dependency checks | Additional HealthCheck CDI beans |
| Silent scheduler job detection | Heartbeat ping after successful @Scheduled run |
| Instant incident alerts | Slack/Discord webhook notifications |
| README status badge | Vigilmon badge embed |
The whole setup runs on Vigilmon's free tier and takes under 30 minutes. Your health endpoint returns accurate 503s when dependencies fail, Vigilmon probes it from multiple regions, and your scheduled jobs ping in on every successful run. The next silent failure gets caught before your users find it.
Monitor your Quarkus app free at vigilmon.online — monitors running in under a minute, no credit card required.