tutorial

health_wicket.py

--- title: "How to Monitor Apache Wicket Web Application Health with Vigilmon" published: false description: Apache Wicket component tree corruption, sessio...


title: "How to Monitor Apache Wicket Web Application Health with Vigilmon" published: false description: Apache Wicket component tree corruption, session serialization failures, and page expiry under load are invisible to generic HTTP checks. Learn how to monitor Wicket application health, session state, and page rendering liveness with Vigilmon HTTP probes and heartbeat monitors. tags: wicket, java, web, monitoring, components, devops cover_image:

Apache Wicket is a component-based Java web framework that models the web page as a Java object tree. Unlike request-response frameworks, Wicket maintains a server-side component tree per session — when that state is corrupted or the backing store becomes unavailable, users encounter cryptic "page expired" errors or blank pages rather than a clean error response. A Wicket application that appears healthy by HTTP status codes can be silently failing all component renders behind a catch-all error page.

Vigilmon gives you external visibility into Wicket application health through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.


Why Wicket Needs External Monitoring

Wicket's stateful component model introduces failure modes that stateless frameworks don't have:

  • Page store exhaustion: Wicket stores serialized page state in a IPageStore (default: disk-based DiskPageStore); when the page store fills up or the disk fails, all page lookups throw PageStorageException — users see "page expired" on every back-navigation
  • Session serialization failures: Wicket serializes page trees into the HTTP session; a non-serializable component (third-party widget, Lambda reference) causes silent session corruption that only surfaces when the session is distributed or restored
  • Component tree version mismatches: after a rolling deployment, a session created on the old version may reference component IDs that no longer exist in the new version — every request for that session returns 400 or an error page
  • Ajax request failures: Wicket's Ajax layer uses component IDs embedded in URLs; stale Ajax requests after a deployment cause ComponentNotFoundException that shows as a broken UI, not an HTTP error
  • Memory pressure from page store: Wicket's default page store keeps N pages per session in memory; under high concurrency, JVM heap fills with component trees while GC pauses cause timeout cascades

External monitoring with Vigilmon adds:

  • Proactive alerting when Wicket's health endpoint reports page store or session issues
  • Heartbeat monitoring to catch silent render failures invisible to HTTP status checks
  • Multi-region availability probing from outside your network
  • Response time thresholds that catch GC-induced latency before it becomes a user-visible outage

Step 1: Build a Wicket Health Endpoint

Wicket does not include a built-in health endpoint. Expose one through a Wicket WebPage mapped to a health path, or via a standalone servlet alongside the Wicket filter.

Wicket Health Page (Wicket WebPage)

// HealthPage.java
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.Application;
import org.apache.wicket.page.IPageManager;
import java.util.*;

public class HealthPage extends WebPage {

    @Override
    protected void configureResponse(WebResponse response) {
        super.configureResponse(response);
        response.setContentType("application/json");
    }

    @Override
    public void renderPage() {
        WebResponse response = (WebResponse) getResponse();
        Map<String, Object> result = new LinkedHashMap<>();

        try {
            Application app = Application.get();
            if (app == null) {
                writeJson(response, 503, Map.of("status", "down", "reason", "application_null"));
                return;
            }

            // Check page store by attempting a no-op access
            IPageManager pageManager = getSession().getPageManager();
            result.put("status", "ok");
            result.put("application", app.getName());
            result.put("session_id", getSession().getId());

            writeJson(response, 200, result);
        } catch (Exception e) {
            writeJson(response, 503, Map.of(
                "status", "down",
                "reason", "wicket_error",
                "error", e.getMessage()
            ));
        }
    }

    private void writeJson(WebResponse response, int status, Map<String, Object> data) {
        response.setStatus(status);
        StringBuilder sb = new StringBuilder("{");
        data.forEach((k, v) -> sb.append('"').append(k).append("\":\"").append(v).append("\","));
        if (sb.charAt(sb.length() - 1) == ',') sb.deleteCharAt(sb.length() - 1);
        sb.append('}');
        response.write(sb.toString());
    }
}

Register the health page in your WicketApplication:

// WicketApplication.java
@Override
public void init() {
    super.init();
    mountPage("/health/wicket", HealthPage.class);
    // ... other mounts
}

Standalone Health Servlet (No Wicket Session Required)

// WicketHealthServlet.java
import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;

public class WicketHealthServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        resp.setContentType("application/json");

        try {
            // Check that page store directory is writable
            String pageStorePath = System.getProperty("wicket.pagestore.path",
                System.getProperty("java.io.tmpdir"));
            File pageStoreDir = new File(pageStorePath);

            if (!pageStoreDir.canWrite()) {
                resp.setStatus(503);
                resp.getWriter().write("{\"status\":\"down\",\"reason\":\"page_store_not_writable\"}");
                return;
            }

            resp.setStatus(200);
            resp.getWriter().write("{\"status\":\"ok\",\"page_store\":\"" + pageStorePath + "\"}");
        } catch (Exception e) {
            resp.setStatus(503);
            resp.getWriter().write("{\"status\":\"down\",\"error\":\"" + e.getMessage() + "\"}");
        }
    }
}

Register the servlet in web.xml or via annotation — map it to /health/wicket-sys outside the Wicket filter chain so it responds even when Wicket is broken.

Spring Boot Actuator Health Indicator (Wicket + Spring Boot)

// WicketHealthIndicator.java
import org.apache.wicket.Application;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import java.io.File;

@Component
public class WicketHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        try {
            Application app = Application.get();
            if (app == null) {
                return Health.down().withDetail("reason", "wicket_application_null").build();
            }

            // Check page store writability
            String storePath = System.getProperty("wicket.pagestore.path",
                System.getProperty("java.io.tmpdir"));
            File storeDir = new File(storePath);

            if (!storeDir.canWrite()) {
                return Health.down()
                    .withDetail("reason", "page_store_not_writable")
                    .withDetail("path", storePath)
                    .build();
            }

            return Health.up()
                .withDetail("application", app.getName())
                .withDetail("page_store_path", storePath)
                .withDetail("free_disk_mb", storeDir.getFreeSpace() / 1_048_576)
                .build();
        } catch (Exception e) {
            return Health.down().withException(e).build();
        }
    }
}

Node.js Sidecar

// health/wicket.js
const express = require('express');
const http = require('http');

const app = express();
const APP_HOST = process.env.APP_HOST || 'localhost';
const APP_PORT = parseInt(process.env.APP_PORT || '8080');

function probe(path) {
  return new Promise((resolve, reject) => {
    const req = http.get(
      { host: APP_HOST, port: APP_PORT, path, timeout: 5000 },
      res => {
        let body = '';
        res.on('data', c => { body += c; });
        res.on('end', () => resolve({ status: res.statusCode, body }));
      }
    );
    req.on('error', reject);
    req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
  });
}

app.get('/health/wicket', async (req, res) => {
  try {
    const health = await probe('/health/wicket');
    if (health.status !== 200) {
      return res.status(503).json({
        status: 'down',
        app_status: health.status,
        body: health.body.substring(0, 300),
      });
    }
    return res.status(200).json({ status: 'ok' });
  } catch (err) {
    return res.status(503).json({ status: 'down', error: err.message });
  }
});

app.listen(3012);

Python Health Sidecar

import os, requests
from flask import Flask, jsonify

app = Flask(__name__)

APP_BASE = f"http://{os.environ.get('APP_HOST', 'localhost')}:{os.environ.get('APP_PORT', '8080')}"

@app.route('/health/wicket')
def health():
    try:
        resp = requests.get(f'{APP_BASE}/health/wicket', timeout=5)
        if resp.status_code != 200:
            return jsonify({'status': 'down', 'app_status': resp.status_code, 'body': resp.text[:300]}), 503

        data = resp.json()
        if data.get('status') not in ('ok', 'UP'):
            return jsonify({'status': 'degraded', 'detail': data}), 503

        return jsonify({'status': 'ok', 'application': data.get('application', 'unknown')})
    except Exception as e:
        return jsonify({'status': 'down', 'error': str(e)}), 503

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3012)

Step 2: Configure Vigilmon HTTP Monitor for Wicket

  1. Log in to vigilmon.online and go to Monitors → New Monitor
  2. Choose HTTP / HTTPS
  3. Set the URL to your Wicket health endpoint: https://your-app.example.com/health/wicket
  4. Set the check interval to 1 minute
  5. Under Expected response, configure:
    • Status code: 200
    • Response body contains: "status":"ok" or "status":"UP"
    • Response time threshold: 5000ms (Wicket page rendering is heavier than REST endpoints)
  6. Under Alert channels, assign your Slack or PagerDuty channel
  7. Save the monitor

Add a second monitor targeting the standalone system health servlet (outside the Wicket filter):

  • URL: https://your-app.example.com/health/wicket-sys
  • Expected: 200, body contains: "status":"ok"
  • Interval: 2 minutes
  • Alert channel: P1 pager — a system-level failure means the Wicket container itself is broken

Vigilmon's multi-region probe consensus distinguishes transient page store blips from persistent failures.


Step 3: Heartbeat Monitoring for Page Render Success

Health endpoint monitoring catches Wicket infrastructure failures, but not silent render failures — a misconfigured error handler can catch all ComponentNotFoundException errors and return 200 with an error page. Vigilmon heartbeat monitors catch these by requiring your application to actively report successful renders.

Set Up the Heartbeat Monitor

  1. In Vigilmon, go to Monitors → New Monitor → Heartbeat
  2. Set the name: wicket-page-render
  3. Set the expected interval: 5 minutes
  4. Set the grace period: 15 minutes
  5. Save — copy the heartbeat URL, e.g. https://vigilmon.online/heartbeat/abc123xyz

Wire It Into a Wicket RequestCycle Listener

// VigilmonRequestCycleListener.java
import org.apache.wicket.request.cycle.IRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
import java.net.http.*;
import java.net.URI;
import java.util.concurrent.atomic.*;

public class VigilmonRequestCycleListener implements IRequestCycleListener {

    private final HttpClient http = HttpClient.newHttpClient();
    private final String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");
    private final AtomicLong lastPingMs = new AtomicLong(0);
    private static final long PING_INTERVAL_MS = 60_000;

    @Override
    public void onEndRequest(RequestCycle cycle) {
        // Only ping on successful, non-error requests
        Integer statusCode = cycle.getResponse() instanceof
            org.apache.wicket.request.http.WebResponse wr ? wr.getStatus() : null;

        if (statusCode != null && statusCode < 400) {
            long now = System.currentTimeMillis();
            if (now - lastPingMs.get() > PING_INTERVAL_MS) {
                lastPingMs.set(now);
                pingHeartbeat();
            }
        }
    }

    @Override
    public IRequestCycleListener.IRequestHandler onException(
            RequestCycle cycle, Exception ex) {
        // Let heartbeat lapse on exceptions — do not ping
        return null;
    }

    private void pingHeartbeat() {
        if (heartbeatUrl == null) return;
        try {
            http.sendAsync(
                HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
                HttpResponse.BodyHandlers.discarding()
            );
        } catch (Exception ignored) {}
    }
}

Register the listener in WicketApplication.init():

// WicketApplication.java
@Override
public void init() {
    super.init();
    getRequestCycleListeners().add(new VigilmonRequestCycleListener());
}

Step 4: Alert Routing for Wicket Failures

Wicket failures range from a full page store outage (all stateful pages broken) to a session serialization regression (intermittent errors after deployment) to GC pressure (slow renders under load). Route alerts by severity:

| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: /health/wicket | Slack + PagerDuty | P1 | | HTTP: /health/wicket-sys | Slack + PagerDuty | P1 | | Heartbeat: page render success | Slack + PagerDuty | P1 | | Heartbeat: background job | Slack + email | P2 |

Set response time thresholds as early warning signals:

  • Alert at 3000ms for the Wicket health page (slow health page means the component tree is large or GC is active)
  • Alert at 500ms for the system health servlet (this is a simple file check; slowness indicates OS-level issues)
  • Alert at 5 minutes gap in page render heartbeat during business hours

For production Wicket deployments, set up a status page in Vigilmon showing page store health, session availability, and render liveness — this lets on-call engineers quickly distinguish a page store disk failure from a session serialization regression introduced by a deployment.


Summary

Wicket's stateful component model turns infrastructure failures into opaque user-facing errors like "page expired" or blank pages. External monitoring catches the underlying causes before they affect users:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /health/wicket | Application-level Wicket health, page manager state | | HTTP monitor on /health/wicket-sys | Page store writability, disk availability | | Heartbeat monitor (request cycle) | End-to-end page render success rate | | Heartbeat monitor (background jobs) | Scheduled task liveness |

Get started free at vigilmon.online — your first Wicket application monitor is running in under two minutes.

Monitor your app with Vigilmon

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

Start free →