tutorial

How to Monitor Your Apache Batik SVG Service (Free, Multi-Region)

Apache Batik powers SVG transcoding, rendering, and manipulation in Java apps. Learn how to add external uptime monitoring, heartbeat checks for batch SVG jobs, and instant alerts — free.

How to Monitor Your Apache Batik SVG Service (Free, Multi-Region)

Apache Batik is the go-to Java toolkit for parsing, rendering, and transcoding SVG content — converting SVGs to PNG/JPEG/PDF, building dynamic chart generation services, and validating SVG input in document pipelines. When a Batik-powered service goes down, your generated graphics disappear, your export pipeline stalls, and your downstream consumers get broken images or empty responses.

By the end of this guide you'll have external uptime monitoring, multi-region probes, heartbeat monitoring for batch transcoding jobs, and instant alerts — all on the free tier.


Why Batik services fail silently

SVG processing services have failure modes that don't always emit obvious signals:

Transcoding endpoint failures — a POST to /render/svg might return 500 because of a malformed input SVG, a missing font, or a TranscoderException. But between retries and silent client-side fallbacks, nobody files a bug report.

Memory pressure — SVG parsing is memory-intensive. Under load, large SVG inputs can push the JVM into OutOfMemoryError territory. The JVM may keep accepting connections while individual requests fail.

Batch transcoding jobs stall — nightly PDF generation, icon sprite compilation, or image export pipelines run without HTTP. When they crash partway through, the partial output may look complete on disk, and no 500 is emitted.


Step 1: Expose a health endpoint that exercises the Batik pipeline

Returning a hardcoded {"status":"UP"} doesn't prove Batik works. Render a trivial SVG to verify the transcoder is functional.

With Spring Boot:

import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
import java.util.Map;

@RestController
public class HealthController {

    private static final String MINIMAL_SVG =
        "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'>" +
        "<circle cx='0.5' cy='0.5' r='0.5'/></svg>";

    @GetMapping("/health")
    public Map<String, String> health() throws Exception {
        PNGTranscoder transcoder = new PNGTranscoder();
        TranscoderInput input = new TranscoderInput(
            new java.io.StringReader(MINIMAL_SVG)
        );
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        TranscoderOutput output = new TranscoderOutput(bos);
        transcoder.transcode(input, output); // throws on failure
        return Map.of("status", "UP", "engine", "ok", "outputBytes",
            String.valueOf(bos.size()));
    }
}

Without Spring (plain servlet):

import org.apache.batik.transcoder.*;
import org.apache.batik.transcoder.image.PNGTranscoder;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

@WebServlet("/health")
public class HealthServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res)
            throws IOException {
        res.setContentType("application/json");
        try {
            PNGTranscoder t = new PNGTranscoder();
            String svg = "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'/>";
            TranscoderInput in = new TranscoderInput(new StringReader(svg));
            TranscoderOutput out = new TranscoderOutput(new ByteArrayOutputStream());
            t.transcode(in, out);
            res.getWriter().write("{\"status\":\"UP\"}");
        } catch (TranscoderException e) {
            res.setStatus(500);
            res.getWriter().write("{\"status\":\"DOWN\",\"error\":\"" + e.getMessage() + "\"}");
        }
    }
}

A 1×1 SVG is cheap to transcode — it proves the Batik classpath, font subsystem, and transcoder pipeline are all intact without wasting CPU on a full-size render.


Step 2: Set up external monitoring with Vigilmon

With /health 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 checks from multiple geographic regions. If it gets a non-2xx or a timeout, it opens an incident and alerts you immediately.

Add monitors for each surface:

| Endpoint | What it catches | |---|---| | /health | Batik transcoder failures, classpath issues | | /render/svg | Core SVG-to-PNG endpoint | | /export/pdf | PDF export pipeline |


Step 3: Heartbeat monitoring for batch transcoding jobs

Nightly icon exports, PDF generation, and report pipelines run outside HTTP — nothing to probe externally. Heartbeat monitoring fills this gap.

import org.apache.batik.apps.rasterizer.*;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

public class NightlyIconExportJob {

    private final String heartbeatUrl =
        System.getenv("HEARTBEAT_ICON_EXPORT_URL");

    public void run() throws Exception {
        File inputDir = new File("/data/svg-sources");
        File outputDir = new File("/data/png-exports");
        outputDir.mkdirs();

        File[] svgFiles = inputDir.listFiles((d, n) -> n.endsWith(".svg"));
        if (svgFiles == null || svgFiles.length == 0) {
            throw new IllegalStateException("No SVG source files found");
        }

        SVGConverter converter = new SVGConverter();
        converter.setDestinationType(DestinationType.PNG);
        converter.setWidth(512f);
        converter.setHeight(512f);
        converter.setDst(outputDir);

        for (File svg : svgFiles) {
            converter.setSources(new String[]{svg.getAbsolutePath()});
            converter.execute();
        }

        // Only ping after all files converted successfully
        if (heartbeatUrl != null && !heartbeatUrl.isEmpty()) {
            pingHeartbeat(heartbeatUrl);
        }
    }

    private void pingHeartbeat(String url) throws Exception {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5_000);
        conn.getResponseCode();
        conn.disconnect();
    }
}

In Vigilmon:

  1. Click New Monitor → Heartbeat
  2. Set the expected interval (e.g. 25 hours for a daily job)
  3. Copy the unique ping URL
  4. Set the environment variable: HEARTBEAT_ICON_EXPORT_URL=https://vigilmon.online/api/heartbeat/your-unique-token

If the SVG converter throws a SVGConverterException, runs out of memory, or exits early, the heartbeat is never pinged, and Vigilmon alerts you within one missed interval.


Step 4: Track memory pressure with a custom health indicator

Batik transcoding is memory-intensive. Add a heap check to your health endpoint:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;

@RestController
public class HealthController {

    private static final double HEAP_WARN_THRESHOLD = 0.85;

    @GetMapping("/health")
    public Map<String, Object> health() throws Exception {
        Map<String, Object> status = new LinkedHashMap<>();

        // Batik engine check
        transcodeTrivialSvg(); // throws on failure
        status.put("engine", "ok");

        // Heap pressure check
        Runtime rt = Runtime.getRuntime();
        long used = rt.totalMemory() - rt.freeMemory();
        long max = rt.maxMemory();
        double ratio = (double) used / max;
        status.put("heapUsedPct", Math.round(ratio * 100));
        status.put("status", ratio > HEAP_WARN_THRESHOLD ? "WARN" : "UP");

        return status;
    }

    private void transcodeTrivialSvg() throws Exception {
        // same 1×1 SVG transcode as Step 1
    }
}

Return HTTP 200 for both UP and WARN, and HTTP 500 for DOWN — so Vigilmon alerts only on hard failures, not memory warnings that you monitor separately.


Step 5: 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 get an instant alert on failure and a recovery notification when the service comes back.

Add an uptime badge:

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

What you've built

| What | How | |---|---| | External health checks | /health with live transcode + Vigilmon HTTP monitor | | Transcoder validation | Trivial 1×1 SVG round-trip in health endpoint | | Batch job monitoring | Heartbeat ping after each successful export run | | Memory pressure tracking | Heap ratio check in health response | | Instant alerts | Slack/Discord notifications | | README status badge | Vigilmon badge embed |

The whole setup runs on the free tier in under 30 minutes. You'll catch Batik pipeline failures, JVM memory problems, and silently stalled batch export jobs before users report missing images or broken graphics.


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 →