tutorial

How to Monitor Apache Jackrabbit Content Repository Health with Vigilmon

Apache Jackrabbit repository unavailability, session pool exhaustion, and workspace access failures are silent until content operations start returning errors. Learn how to monitor Jackrabbit repository health, workspace availability, and observation listener liveness with Vigilmon HTTP probes and heartbeat monitors.

Apache Jackrabbit is the reference implementation of the Java Content Repository (JCR) API, providing hierarchical content storage for Java applications, CMS platforms, and enterprise document management systems. Jackrabbit sits beneath every content operation — when the repository is unreachable, all reads and writes fail silently or throw RepositoryException; when a workspace becomes inaccessible, content migrations hang indefinitely; when the observation listener queue fills, event-driven workflows stop firing without any visible error. These are production incidents disguised as application-level content errors.

Vigilmon gives you external visibility into Jackrabbit's content layer through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.


Why Jackrabbit Needs External Monitoring

Jackrabbit's failure modes are subtle and high-impact:

  • Repository session pool exhaustion: Jackrabbit sessions are heavyweight resources; a session leak causes new Repository.login() calls to block or throw RepositoryException while the application appears to be running normally
  • Workspace corruption detection delay: workspace-level issues do not bubble up until a specific path is traversed — a corrupted workspace can silently affect a subset of content operations for hours
  • Observation listener queue overflow: Jackrabbit's event observation queue has a bounded size; when it fills, new events are dropped silently and event-driven content workflows stop triggering without any error in application logs
  • Versioning service failures: Jackrabbit's version manager is separate from the node storage layer — a versioning backend failure causes save() calls to fail without indicating the root cause
  • Persistence manager connectivity: the persistence manager can become unreachable while the Jackrabbit process continues running; all subsequent node modifications fail at commit time
  • Index synchronization lag: Jackrabbit's Lucene-based search index can fall behind the persistence store; queries return stale or empty results while content appears to save successfully

External monitoring with Vigilmon adds:

  • Proactive alerting when repository health endpoints report session or workspace failures
  • Persistence backend availability monitoring independent of the application layer
  • Heartbeat monitoring so you know when content write-and-read cycles stop completing
  • Multi-region probe consensus that filters transient blips from genuine repository failures

Step 1: Build a Jackrabbit Health Endpoint

Jackrabbit does not ship with a built-in HTTP health endpoint. Add one via a custom servlet, Spring Boot Actuator, or a lightweight sidecar.

Spring Boot Actuator Health Indicator

// JackrabbitHealthIndicator.java
import javax.jcr.*;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class JackrabbitHealthIndicator implements HealthIndicator {

    private final Repository repository;
    private final String workspaceName;

    public JackrabbitHealthIndicator(Repository repository, String workspaceName) {
        this.repository = repository;
        this.workspaceName = workspaceName;
    }

    @Override
    public Health health() {
        Session session = null;
        try {
            session = repository.login(
                new SimpleCredentials("health", "health".toCharArray()),
                workspaceName
            );

            Node root = session.getRootNode();
            return Health.up()
                .withDetail("workspace", workspaceName)
                .withDetail("root_path", root.getPath())
                .withDetail("repo_descriptor",
                    repository.getDescriptor(Repository.REP_NAME_DESC))
                .build();

        } catch (LoginException e) {
            return Health.down()
                .withDetail("reason", "credentials_rejected")
                .withDetail("error", e.getMessage())
                .build();
        } catch (RepositoryException e) {
            return Health.down()
                .withDetail("reason", "repository_unavailable")
                .withDetail("error", e.getMessage())
                .build();
        } finally {
            if (session != null) session.logout();
        }
    }
}

This exposes /actuator/health/jackrabbit automatically via Spring Boot Actuator.

Custom Workspace Connectivity Check

// JackrabbitHealthController.java
import javax.jcr.*;
import org.springframework.web.bind.annotation.*;
import java.util.*;

@RestController
@RequestMapping("/health")
public class JackrabbitHealthController {

    private final Repository repository;
    private final List<String> workspaces;

    public JackrabbitHealthController(Repository repository, List<String> workspaces) {
        this.repository = repository;
        this.workspaces = workspaces;
    }

    @GetMapping("/jackrabbit")
    public ResponseEntity<Map<String, Object>> health() {
        Map<String, Object> response = new LinkedHashMap<>();
        List<Map<String, Object>> wsStatus = new ArrayList<>();
        boolean anyFailed = false;

        for (String ws : workspaces) {
            Map<String, Object> status = new LinkedHashMap<>();
            status.put("workspace", ws);
            Session session = null;
            try {
                session = repository.login(
                    new SimpleCredentials("health", "health".toCharArray()), ws);
                session.getRootNode();
                status.put("status", "ok");
            } catch (Exception e) {
                status.put("status", "down");
                status.put("error", e.getMessage());
                anyFailed = true;
            } finally {
                if (session != null) session.logout();
            }
            wsStatus.add(status);
        }

        response.put("workspaces", wsStatus);
        response.put("status", anyFailed ? "degraded" : "ok");

        return anyFailed
            ? ResponseEntity.status(503).body(response)
            : ResponseEntity.ok(response);
    }
}

Node.js Sidecar

// health/jackrabbit.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');
const HEALTH_PATH = process.env.JCR_HEALTH_PATH || '/actuator/health/jackrabbit';

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

app.get('/health/jackrabbit', async (req, res) => {
  try {
    const result = await probeActuator();
    const up = result.status === 200 &&
      (result.body.includes('"status":"UP"') || result.body.includes('"status":"ok"'));

    if (!up) {
      return res.status(503).json({
        status: 'down',
        actuator_status: result.status,
        body: result.body.substring(0, 500),
      });
    }

    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

# health_jackrabbit.py
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')}"
HEALTH_PATH = os.environ.get('JCR_HEALTH_PATH', '/actuator/health/jackrabbit')

@app.route('/health/jackrabbit')
def health():
    try:
        resp = requests.get(f'{APP_BASE}{HEALTH_PATH}', timeout=5)
        if resp.status_code != 200:
            return jsonify({'status': 'down', 'actuator_status': resp.status_code}), 503

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

        return jsonify({
            'status': 'ok',
            'workspace': data.get('details', {}).get('workspace', '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 Jackrabbit

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

Add a second monitor for workspace connectivity:

  • URL: https://your-app.example.com/health/jackrabbit
  • Expected: 200, body contains: "status":"ok"
  • Interval: 2 minutes
  • Alert channel: P1 pager — workspace failure means content operations are failing for all users

Vigilmon's multi-region probe consensus filters transient network blips from genuine Jackrabbit repository failures.


Step 3: Heartbeat Monitoring for Content Write-Read Cycles

Health endpoint monitoring catches repository connectivity failures, but not end-to-end content pipeline problems. A Jackrabbit workspace can report healthy while a persistence manager flush is silently failing at commit time, or while observation listeners are dropping events.

Vigilmon heartbeat monitors catch these: your application pings Vigilmon after each successful content write-and-read cycle.

Set Up the Heartbeat Monitor

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

Wire It Into a Jackrabbit Content Job (Spring Boot)

// JackrabbitPipelineJob.java
import javax.jcr.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.net.http.*;
import java.net.URI;

@Component
public class JackrabbitPipelineJob {

    private final Repository repository;
    private final HttpClient http = HttpClient.newHttpClient();
    private final String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");

    public JackrabbitPipelineJob(Repository repository) {
        this.repository = repository;
    }

    @Scheduled(fixedDelay = 300_000)
    public void runContentCycleCheck() {
        Session session = null;
        try {
            session = repository.login(
                new SimpleCredentials("pipeline", "pipeline".toCharArray()));

            Node root = session.getRootNode();
            String probePath = "vigilmon-probe";
            if (root.hasNode(probePath)) root.getNode(probePath).remove();
            Node probe = root.addNode(probePath, "nt:unstructured");
            probe.setProperty("ts", System.currentTimeMillis());
            session.save();

            Node check = session.getRootNode().getNode(probePath);
            if (check.getProperty("ts").getLong() > 0) {
                pingHeartbeat();
            }

            check.remove();
            session.save();

        } catch (Exception ignored) {
            // Heartbeat lapses naturally on failure
        } finally {
            if (session != null) session.logout();
        }
    }

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

Step 4: Alert Routing for Jackrabbit Failures

Jackrabbit failures range from a single workspace being inaccessible to the persistence manager being completely unreachable. Route alerts by severity:

| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: /actuator/health/jackrabbit | Slack + PagerDuty | P1 | | HTTP: /health/jackrabbit workspace check | Slack + PagerDuty | P1 | | Heartbeat: content write-read cycle | Slack + PagerDuty | P1 | | Heartbeat: observation listener job | Slack + email | P2 |

Set response time thresholds as early warning signals:

  • Alert at 500ms for the Jackrabbit health endpoint (slow responses indicate persistence manager latency)
  • Alert at 2000ms for the workspace connectivity check (slow workspace access precedes full failure)
  • Alert at 30 seconds gap in content cycle heartbeat during peak hours

For production applications, set up a status page in Vigilmon showing repository health and workspace availability — this lets on-call engineers identify content layer failures instantly rather than inferring them from user reports of missing or stale content.


Summary

Jackrabbit failures present as content errors, not infrastructure failures — users see missing documents or stale search results while the real cause is session pool exhaustion or a persistence manager disconnect. External monitoring catches these before they become an incident:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /actuator/health/jackrabbit | Repository connectivity, session availability | | HTTP monitor on /health/jackrabbit | Individual workspace access, persistence backend | | Heartbeat monitor (content pipeline) | End-to-end write-and-read cycle success | | Heartbeat monitor (observation listeners) | Event-driven content workflow liveness |

Get started free at vigilmon.online — your first Jackrabbit repository 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 →