tutorial

How to Monitor Apache Karaf Health and OSGi Bundle State with Vigilmon

Apache Karaf OSGi runtime failures — failed bundles, missing services, feature install errors — are invisible to standard uptime checks. Learn how to monitor Karaf container health, bundle state, and application service liveness with Vigilmon HTTP probes and heartbeat monitors.

Apache Karaf is a modular OSGi runtime container used to deploy Java applications as independently versioned bundles. Unlike monolithic JARs, Karaf applications can partially fail: a single bundle can fail to start, a required OSGi service can go missing, or a feature can install with a broken dependency — while the container itself continues running and responding to health checks. The JVM is up, the port is open, but critical functionality is gone.

Vigilmon gives you external visibility into Karaf container health through HTTP probe monitoring and heartbeat monitors for the services running inside it. This tutorial covers both.


Why Karaf Needs External Monitoring

Karaf's OSGi lifecycle introduces failure modes that no generic TCP or HTTP port check can detect:

  • Bundle failures: a bundle transitions to INSTALLED or RESOLVED instead of ACTIVE — its code is present but not running
  • Missing service dependencies: a bundle is ACTIVE but a required OSGi service it depends on was never registered, so it waits indefinitely
  • Feature install failures: a Karaf feature (a group of bundles + configurations) partially installs, leaving services in an inconsistent state
  • Config Admin changes: a ManagedService receives a bad configuration update and stops processing without crashing the container

External monitoring with Vigilmon adds:

  • Proactive alerting when Karaf's health endpoint reports bundle failures or degraded services
  • Heartbeat monitoring so you know immediately when an OSGi service stops processing work
  • HTTP probe monitoring for custom health endpoints exposed via Karaf's built-in web container
  • Multi-region availability checking from outside your network perimeter

Step 1: Build a Karaf Health Endpoint

Karaf includes a built-in web container (via karaf-webconsole and pax-web) and optionally exposes a REST health endpoint. The most reliable approach is to deploy a custom health bundle.

Karaf REST Health Bundle (Blueprint + CXF)

// src/main/java/com/example/health/KarafHealthResource.java
import org.apache.felix.utils.manifest.ManifestParser;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.*;

@Path("/health")
public class KarafHealthResource {

    private BundleContext bundleContext;

    public void setBundleContext(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }

    @GET
    @Path("/karaf")
    @Produces(MediaType.APPLICATION_JSON)
    public Response health() {
        if (bundleContext == null) {
            return Response.status(503).entity(Map.of("status", "down", "reason", "bundle_context_unavailable")).build();
        }

        Bundle[] bundles = bundleContext.getBundles();
        List<Map<String, Object>> failed = new ArrayList<>();
        List<Map<String, Object>> fragmentsOk = new ArrayList<>();

        for (Bundle bundle : bundles) {
            int state = bundle.getState();
            String name = bundle.getSymbolicName();

            // Fragments are RESOLVED not ACTIVE — that's correct
            boolean isFragment = bundle.getHeaders().get("Fragment-Host") != null;

            if (isFragment) {
                if (state != Bundle.RESOLVED) {
                    failed.add(Map.of("name", name, "state", stateString(state), "fragment", true));
                }
                continue;
            }

            if (state != Bundle.ACTIVE) {
                failed.add(Map.of("name", name, "state", stateString(state), "fragment", false));
            }
        }

        if (!failed.isEmpty()) {
            return Response.status(503).entity(Map.of(
                "status", "degraded",
                "reason", "bundles_not_active",
                "failed_bundles", failed,
                "total_bundles", bundles.length
            )).build();
        }

        return Response.ok(Map.of(
            "status", "ok",
            "total_bundles", bundles.length,
            "all_active", true
        )).build();
    }

    private String stateString(int state) {
        return switch (state) {
            case Bundle.UNINSTALLED -> "UNINSTALLED";
            case Bundle.INSTALLED -> "INSTALLED";
            case Bundle.RESOLVED -> "RESOLVED";
            case Bundle.STARTING -> "STARTING";
            case Bundle.STOPPING -> "STOPPING";
            case Bundle.ACTIVE -> "ACTIVE";
            default -> "UNKNOWN(" + state + ")";
        };
    }
}

Register this in your Blueprint XML:

<!-- OSGI-INF/blueprint/health.xml -->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
  <bean id="karafHealthResource" class="com.example.health.KarafHealthResource">
    <property name="bundleContext" ref="blueprintBundleContext"/>
  </bean>

  <jaxrs:server id="healthServer" address="/api">
    <jaxrs:serviceBeans>
      <ref component-id="karafHealthResource"/>
    </jaxrs:serviceBeans>
  </jaxrs:server>
</blueprint>

Using Karaf's Built-In Health Check (Karaf 4.4+)

Karaf 4.4+ includes karaf:health-check as part of the karaf-features feature. Install it and query:

# Install the health check feature
feature:install karaf-health-check

# Query the REST endpoint (default port 8181)
curl http://localhost:8181/health

The built-in health check returns bundle state, service availability, and system thread pool saturation. Point Vigilmon directly at this endpoint.

Python Health Sidecar (querying Karaf REST or SSH)

# health_karaf.py
import os, subprocess, json
from flask import Flask, jsonify

app = Flask(__name__)

KARAF_HOST = os.environ.get('KARAF_HOST', 'localhost')
KARAF_SSH_PORT = int(os.environ.get('KARAF_SSH_PORT', '8101'))
KARAF_USER = os.environ.get('KARAF_USER', 'karaf')
KARAF_PASS = os.environ.get('KARAF_PASS', 'karaf')

@app.route('/health/karaf')
def health():
    try:
        # Query Karaf via SSH using sshpass + karaf client
        result = subprocess.run(
            ['sshpass', '-p', KARAF_PASS,
             'ssh', '-o', 'StrictHostKeyChecking=no',
             '-p', str(KARAF_SSH_PORT),
             f'{KARAF_USER}@{KARAF_HOST}',
             'bundle:list -t 0 --no-format'],
            capture_output=True, text=True, timeout=10
        )

        if result.returncode != 0:
            return jsonify({'status': 'down', 'reason': 'ssh_failed', 'error': result.stderr}), 503

        lines = result.stdout.splitlines()
        failed = []
        for line in lines:
            # Parse bundle:list output: "ID | State | Level | Name"
            parts = [p.strip() for p in line.split('|')]
            if len(parts) >= 3 and parts[1] not in ('Active', 'Resolved', 'State'):
                failed.append({'line': line.strip(), 'state': parts[1]})

        if failed:
            return jsonify({'status': 'degraded', 'reason': 'bundles_not_active', 'failed': failed}), 503

        return jsonify({'status': 'ok', 'total_lines': len(lines)})
    except Exception as e:
        return jsonify({'status': 'down', 'error': str(e)}), 503

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

Step 2: Configure Vigilmon HTTP Monitor for Karaf

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

Add a second monitor for the Karaf web console itself:

  • URL: https://your-app.example.com:8181/system/console
  • Expected: 200 (returns the login page when Karaf is healthy)
  • Interval: 2 minutes
  • Alert channel: Slack for ops awareness

Vigilmon's multi-region probe consensus means a single network blip won't page your team.


Step 3: Heartbeat Monitoring for Karaf OSGi Services

Bundle state monitoring catches failed bundles, but not stalled services. An ACTIVE bundle can have all its threads blocked waiting for a downstream resource. A Camel route running inside Karaf can stop polling without triggering any bundle state change.

Vigilmon heartbeat monitors detect these silent stalls: your OSGi service pings Vigilmon after each processing cycle.

Set Up the Heartbeat Monitor

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

Wire It Into an OSGi Service (Blueprint)

// src/main/java/com/example/processor/MessageProcessor.java
import org.osgi.service.component.annotations.*;
import java.net.http.*;
import java.net.URI;
import java.util.concurrent.*;

@Component(immediate = true, service = MessageProcessor.class)
public class MessageProcessor {

    private final HttpClient http = HttpClient.newHttpClient();
    private ScheduledExecutorService scheduler;

    @Reference
    private QueueService queueService;

    @Activate
    public void activate() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleWithFixedDelay(this::processMessages, 0, 60, TimeUnit.SECONDS);
    }

    @Deactivate
    public void deactivate() {
        if (scheduler != null) scheduler.shutdownNow();
    }

    private void processMessages() {
        try {
            queueService.drain();

            // Signal Vigilmon that the processing cycle completed
            String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");
            if (heartbeatUrl != null) {
                http.sendAsync(
                    HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
                    HttpResponse.BodyHandlers.discarding()
                );
            }
        } catch (Exception e) {
            // Log but don't rethrow — scheduler must keep running
            System.err.println("Processing cycle failed: " + e.getMessage());
        }
    }
}

Wire It Into a Camel Route in Karaf

// src/main/java/com/example/routes/HealthRoute.java
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.bean.BeanComponent;

public class HealthRouteBuilder extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");

        from("timer:heartbeat?period=60000")
            .routeId("vigilmon-heartbeat")
            .to("direct:checkHealth");

        from("direct:checkHealth")
            .process(exchange -> {
                // Verify downstream resources are reachable
                checkDownstreamResources();
            })
            .to("http4:" + heartbeatUrl.replace("https://", "") + "?httpMethod=GET");
    }

    private void checkDownstreamResources() {
        // Add application-specific health checks here
    }
}

Step 4: Alert Routing for Karaf Failures

Karaf failures range from a single stalled bundle (degraded but recoverable) to full container failure (all services down). Route alerts by severity:

| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: /api/health/karaf bundle state | Slack + PagerDuty | P1 | | HTTP: Karaf web console 8181/system/console | Slack + PagerDuty | P1 | | Heartbeat: Camel route processing | Slack + email | P2 | | Heartbeat: OSGi service worker loop | Slack | P2 |

Set response time thresholds as early warning signals:

  • Alert at 3000ms for the bundle health endpoint (slow responses indicate GC pressure or thread pool saturation)
  • Alert at 5000ms for the web console (slow console is often a symptom of an overloaded container)

For production deployments, set up a status page in Vigilmon showing Karaf container health alongside the business services it hosts — this makes it immediately clear whether a service outage is a Karaf container problem or an application-layer problem.


Summary

Karaf failures are OSGi lifecycle failures — the JVM is up, the port is open, but individual bundles and services are dead. External monitoring catches these before they become incidents:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /api/health/karaf | Bundle state, fragment state, ACTIVE count | | HTTP monitor on Karaf web console | Container liveness, admin interface availability | | Heartbeat monitor on service worker | OSGi service processing cycle liveness | | Heartbeat monitor on Camel route | Route execution and downstream resource reachability |

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