tutorial

How to Monitor Your Apache Velocity Application (Free, Multi-Region)

Apache Velocity powers many Java web apps and code generators, but its rendering pipeline can fail silently. Learn how to add external uptime monitoring, heartbeat checks for batch generation jobs, and instant alerts — free.

How to Monitor Your Apache Velocity Application (Free, Multi-Region)

Apache Velocity is a Java-based template engine used in web applications, email generation, code generation pipelines, and content-heavy backends. When the servlet that renders Velocity templates goes down, your users see blank pages or 500 errors — and your generation jobs silently stop producing output.

By the end of this guide you'll have external uptime monitoring, multi-region health checks, heartbeat monitoring for batch rendering jobs, and a public status page — all on the free tier.


Why Velocity-powered apps fail silently

Java web apps using Velocity have two common failure patterns:

HTTP rendering failures — the VelocityEngine loads at startup but a missing template, a broken classpath, or a NullPointerException inside a template directive causes individual requests to fail with 500s. The JVM keeps running; the failures pile up unnoticed.

Background generation jobs stall — nightly email generation, report rendering, or code-gen pipelines use VelocityEngine directly without any HTTP layer. When they throw and exit early, there is no 500 to observe — the files simply never appear.

External monitoring catches both patterns when you expose a health endpoint and attach a heartbeat to every generation job.


Step 1: Add a health check servlet or endpoint

The simplest health probe for a Velocity web app is a dedicated /health route that renders a trivial template. If the template engine is broken, this route will fail too.

With Spring MVC:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.VelocityContext;
import java.io.StringWriter;
import java.util.Map;

@RestController
public class HealthController {

    private final VelocityEngine velocityEngine;

    public HealthController(VelocityEngine velocityEngine) {
        this.velocityEngine = velocityEngine;
    }

    @GetMapping("/health")
    public Map<String, String> health() {
        // Try rendering a trivial template to verify the engine is working
        VelocityContext ctx = new VelocityContext();
        ctx.put("status", "UP");
        StringWriter out = new StringWriter();
        velocityEngine.evaluate(ctx, out, "health-check", "status: $status");
        return Map.of("status", "UP", "engine", "ok");
    }
}

With a plain servlet (no Spring):

import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.VelocityContext;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

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

    private VelocityEngine ve;

    @Override
    public void init() throws ServletException {
        ve = new VelocityEngine();
        ve.setProperty("resource.loader", "class");
        ve.setProperty("class.resource.loader.class",
            "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        ve.init();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res)
            throws IOException {
        res.setContentType("application/json");
        VelocityContext ctx = new VelocityContext();
        ctx.put("ok", true);
        StringWriter sw = new StringWriter();
        ve.evaluate(ctx, sw, "health", "{\"status\":\"$ok\"}");
        res.getWriter().write("{\"status\":\"UP\"}");
    }
}

The key is that this endpoint exercises the VelocityEngine rather than just returning a hardcoded string — that way a broken template path or a misconfigured engine will surface as a 500 rather than a false positive.


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. A timeout or non-2xx response opens an incident and alerts you immediately.

Add monitors for each surface:

| Endpoint | What it catches | |---|---| | /health | VelocityEngine init failure, classpath issues | | / | Home page rendering failure | | /api/render | Core template rendering endpoint |


Step 3: Heartbeat monitoring for batch generation jobs

Nightly email campaigns, weekly reports, and code-gen pipelines run outside any HTTP request cycle — there's no response code to monitor. Heartbeat monitoring fills this gap.

The pattern: your job pings a unique URL at the end of each successful run. Vigilmon alerts you if the ping stops arriving within the expected window.

import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.Template;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

public class NightlyEmailJob {

    private final VelocityEngine ve;
    private final String heartbeatUrl = System.getenv("HEARTBEAT_EMAIL_JOB_URL");

    public NightlyEmailJob(VelocityEngine ve) {
        this.ve = ve;
    }

    public void run() throws Exception {
        // Load and render templates for all pending emails
        Template tpl = ve.getTemplate("email/weekly-digest.vm");
        for (User user : getActiveUsers()) {
            VelocityContext ctx = new VelocityContext();
            ctx.put("user", user);
            ctx.put("items", getDigestItems(user));
            StringWriter out = new StringWriter();
            tpl.merge(ctx, out);
            sendEmail(user.getEmail(), out.toString());
        }

        // Only ping on full success
        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(5000);
        conn.getResponseCode();
        conn.disconnect();
    }

    // ... getActiveUsers(), getDigestItems(), sendEmail() omitted
}

In Vigilmon:

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

Now if the template fails to render, an email fails to send, or the job exits early, Vigilmon fires within one missed interval.


Step 4: Monitor template loading at startup

Velocity loads templates lazily by default. A missing template causes a 500 only when that specific route is first hit — potentially hours after deploy. Force early validation:

import org.apache.velocity.app.VelocityEngine;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class VelocityTemplateValidator {

    private final VelocityEngine ve;

    // List every critical template your app needs
    private static final String[] REQUIRED_TEMPLATES = {
        "layout/base.vm",
        "email/welcome.vm",
        "email/weekly-digest.vm",
        "pages/home.vm"
    };

    public VelocityTemplateValidator(VelocityEngine ve) {
        this.ve = ve;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void validate() {
        for (String tpl : REQUIRED_TEMPLATES) {
            if (!ve.resourceExists(tpl)) {
                throw new IllegalStateException("Required Velocity template missing: " + tpl);
            }
        }
    }
}

This forces a startup crash instead of a runtime 500, making your /health check immediately reflect a deploy-time template problem.


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 get an instant alert when a monitor trips and a recovery notification when it comes back.

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=velocity-tutorial)

What you've built

| What | How | |---|---| | External health checks | /health endpoint + Vigilmon HTTP monitor | | Engine validation | Trivial template render inside health check | | Batch job monitoring | Heartbeat ping after each successful generation run | | Startup validation | ApplicationReadyEvent template existence check | | Instant alerts | Slack/Discord notifications | | README status badge | Vigilmon badge embed |

The setup runs entirely on the free tier and takes under 30 minutes. You'll catch template failures, engine errors, and silently stalled generation jobs before your users or stakeholders notice.


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 →