tutorial

How to Monitor Apache Johnzon Applications (Free, Multi-Region)

Apache Johnzon handles JSON-P and JSON-B in Jakarta EE apps — but a failed serialization or a broken downstream JSON API won't alert anyone unless you add external monitoring. Set up uptime checks and heartbeat monitoring in under 30 minutes, free.

How to Monitor Apache Johnzon Applications (Free, Multi-Region)

Apache Johnzon is the reference implementation of JSON-P (JSON Processing) and JSON-B (JSON Binding) for Jakarta EE. It powers JSON serialization in TomEE, OpenLiberty, and other EE containers. But when a bad JSON schema change breaks deserialization or a downstream JSON API goes silent, nothing alerts you unless you set up external monitoring.

By the end of this guide you'll have external uptime monitoring, heartbeat checks for your JSON processing pipelines, and instant alerts — all running on the free tier.


Why Johnzon-backed apps fail silently

Johnzon applications have two common silent failure modes:

Deserialization failures in REST endpoints — a JSON schema change in a upstream API makes Johnzon throw JsonbException on every request to a particular resource. The endpoint returns 500s, but unless your monitoring is watching, your users discover the breakage first.

Batch JSON processing job failures — a nightly job that pulls JSON from an S3 bucket or external API and processes it with JsonReader or Jsonb fails silently when the file format changes or the remote returns an empty document. The job finishes with no output, but nothing fires an alert.

Both are solvable with one external monitoring tool checking from outside your infrastructure.


Step 1: Add a health endpoint

For a Jakarta EE / MicroProfile app, use MicroProfile Health:

<!-- pom.xml -->
<dependency>
    <groupId>org.eclipse.microprofile.health</groupId>
    <artifactId>microprofile-health-api</artifactId>
    <scope>provided</scope>
</dependency>

Write a liveness check that validates Johnzon can round-trip a basic JSON object:

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;

@Liveness
@ApplicationScoped
public class JohnzonHealthCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        try {
            // JSON-P round-trip
            JsonObject obj = Json.createObjectBuilder()
                    .add("ping", "pong")
                    .build();
            String json = obj.toString();
            Json.createReader(new java.io.StringReader(json)).readObject();

            // JSON-B round-trip
            try (Jsonb jsonb = JsonbBuilder.create()) {
                String serialized = jsonb.toJson(new PingDto("ok"));
                jsonb.fromJson(serialized, PingDto.class);
            }

            return HealthCheckResponse.up("johnzon");
        } catch (Exception e) {
            return HealthCheckResponse.named("johnzon")
                    .withData("error", e.getMessage())
                    .down()
                    .build();
        }
    }

    public static class PingDto {
        public String status;
        public PingDto() {}
        public PingDto(String status) { this.status = status; }
    }
}

MicroProfile Health exposes this at /health/live and /health. The endpoint returns HTTP 200 when all checks pass and 503 when any check fails.

For a Spring Boot app using Johnzon, add Spring Actuator instead:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class JohnzonHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        try (Jsonb jsonb = JsonbBuilder.create()) {
            String json = jsonb.toJson(java.util.Map.of("test", true));
            jsonb.fromJson(json, java.util.Map.class);
            return Health.up().withDetail("johnzon", "ok").build();
        } catch (Exception e) {
            return Health.down(e).build();
        }
    }
}

Verify locally:

curl http://localhost:8080/health
# {"status":"UP"}

Step 2: Validate downstream JSON API connectivity

If your app consumes an external JSON API and processes the response with Johnzon, add a readiness check for that dependency:

import org.eclipse.microprofile.health.Readiness;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.inject.Inject;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import jakarta.enterprise.context.ApplicationScoped;

@Readiness
@ApplicationScoped
public class DownstreamApiHealthCheck implements HealthCheck {

    @Inject
    @RestClient
    private DataApiClient dataApiClient;

    @Override
    public HealthCheckResponse call() {
        try {
            dataApiClient.ping(); // lightweight endpoint on your upstream
            return HealthCheckResponse.up("downstream-json-api");
        } catch (Exception e) {
            return HealthCheckResponse.named("downstream-json-api")
                    .withData("error", e.getMessage())
                    .down()
                    .build();
        }
    }
}

When the upstream API is down or returns malformed JSON, this check fails and /health returns 503.


Step 3: Set up external monitoring with Vigilmon

With your health endpoint live, point Vigilmon at it:

  1. Sign up at vigilmon.online — free tier, no credit card
  2. Click New Monitor → HTTP
  3. Enter https://yourdomain.com/health
  4. Set check interval (5 minutes on free tier)
  5. Save

Vigilmon probes from multiple regions. A non-2xx response or timeout opens an incident and alerts you immediately.

Recommended monitors for a Johnzon-backed app:

| Endpoint | What it catches | |---|---| | /health or /health/live | Johnzon serialization broken, downstream API down | | /health/ready | App not yet ready to serve traffic | | /api/v1/data | Your core JSON endpoint broken |


Step 4: Heartbeat monitoring for JSON batch processing jobs

HTTP checks don't catch silent batch failures. Heartbeat monitoring does.

The pattern: your batch job pings a unique URL at the end of each successful run. If Vigilmon stops receiving the ping within the expected window, it fires an alert.

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonReader;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class JsonBatchProcessor {

    private static final String HEARTBEAT_URL =
            System.getenv("HEARTBEAT_JSON_BATCH_URL");

    private final HttpClient httpClient = HttpClient.newHttpClient();

    public void runNightlyJob() throws Exception {
        // Fetch JSON from remote
        HttpResponse<InputStream> response = httpClient.send(
                HttpRequest.newBuilder(URI.create("https://api.example.com/export.json"))
                        .GET().build(),
                HttpResponse.BodyHandlers.ofInputStream()
        );

        if (response.statusCode() != 200) {
            throw new RuntimeException("Remote returned " + response.statusCode());
        }

        // Parse with Johnzon JSON-P
        try (JsonReader reader = Json.createReader(response.body())) {
            JsonArray records = reader.readArray();
            if (records.isEmpty()) {
                throw new RuntimeException("Empty dataset — aborting");
            }
            processRecords(records);
        }

        // Only ping heartbeat after clean success
        pingHeartbeat();
    }

    private void processRecords(JsonArray records) {
        // your ETL logic
    }

    private void pingHeartbeat() {
        if (HEARTBEAT_URL == null || HEARTBEAT_URL.isBlank()) return;
        try {
            httpClient.send(
                    HttpRequest.newBuilder(URI.create(HEARTBEAT_URL)).GET().build(),
                    HttpResponse.BodyHandlers.discarding()
            );
        } catch (Exception ignored) {}
    }
}

In Vigilmon:

  1. Click New Monitor → Heartbeat
  2. Set the expected interval (e.g. 25 hours for a nightly job)
  3. Copy the unique ping URL
  4. Set HEARTBEAT_JSON_BATCH_URL in your environment

If the job throws before pingHeartbeat() is called, the ping is never sent and Vigilmon alerts you within one missed interval.


Step 5: Webhook alerts and badge embed

Slack/Discord alerts:

  1. In Vigilmon go to Notifications → New Channel
  2. Choose Slack or Discord, paste your webhook URL
  3. 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:

[![Uptime](https://vigilmon.online/badge/your-monitor-id.svg)](https://vigilmon.online?utm_source=devto&utm_medium=article&utm_campaign=johnzon-tutorial)

What you've built

| What | How | |---|---| | External health checks | /health + Vigilmon HTTP monitor | | JSON serialization validation | Johnzon round-trip in liveness check | | Downstream API check | MicroProfile readiness probe | | Batch job monitoring | Heartbeat ping after each run | | Instant alerts | Slack/Discord notifications |

The entire setup runs on the free tier in under 30 minutes. The next time a JSON schema change breaks your deserialization, you'll know before users start filing tickets.


Get started free at vigilmon.online — monitors running in under a minute, no credit card required.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →