tutorial

How to Monitor Apache CXF Web Services Health with Vigilmon

Apache CXF SOAP and REST services fail silently — WSDL parsing errors, interceptor chain failures, and JAX-WS handler faults don't always return HTTP 500. Learn how to monitor CXF service health, endpoint liveness, and request processing with Vigilmon HTTP probes and heartbeat monitors.

Apache CXF is the most widely used Java framework for building SOAP and REST web services. It handles WSDL-to-Java code generation, WS-Security, WS-Addressing, MTOM attachments, and dozens of other JAX-WS and JAX-RS standards automatically. But CXF failure modes are subtle: a SOAPFaultException returns HTTP 200 with a fault body, a misconfigured security interceptor can reject all requests silently, and a CXF endpoint bound to the wrong WSDL version can appear healthy while returning malformed responses to every caller.

Vigilmon gives you external visibility into CXF web service health through HTTP probe monitoring and heartbeat monitors for the services behind those endpoints. This tutorial covers both.


Why CXF Needs External Monitoring

CXF's layered interceptor chain and protocol abstraction create failure modes that standard uptime checks miss:

  • SOAP faults over HTTP 200: a SOAP service returns <faultcode> in the response body with HTTP 200 — no port check or generic HTTP monitor catches this
  • Interceptor chain failures: a WS-Security UsernameToken validation failure, an expired wsse:Timestamp, or a schema validation error returns HTTP 500 with a SOAP fault — not the usual 4xx/5xx an HTTP monitor expects
  • WSDL unavailability: the ?wsdl endpoint goes down independently of the service endpoint, breaking all clients that fetch WSDL dynamically
  • Thread pool exhaustion: CXF uses a Jetty or Tomcat thread pool; under load, requests queue indefinitely without returning errors until the timeout fires
  • Bus lifecycle issues: a CXF Bus that fails to initialize (missing feature, bad extension) leaves all endpoints on that bus unreachable

External monitoring with Vigilmon adds:

  • Proactive alerting when CXF WSDL or REST health endpoints return non-200 or unexpected bodies
  • SOAP-aware probe monitoring that checks for fault bodies in 200 responses
  • Heartbeat monitoring for the business operations running behind your CXF endpoints
  • Multi-region availability checking from outside your network perimeter

Step 1: Build a CXF Health Endpoint

CXF doesn't expose an HTTP health interface by default — you need to add one. The simplest approach is a dedicated JAX-RS health resource on the same CXF server.

Java JAX-RS Health Resource (CXF + Spring Boot)

// src/main/java/com/example/health/CxfHealthResource.java
import org.apache.cxf.Bus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.endpoint.ServerRegistry;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.*;

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

    private final Bus bus;

    public CxfHealthResource(Bus bus) {
        this.bus = bus;
    }

    @GET
    @Path("/cxf")
    @Produces(MediaType.APPLICATION_JSON)
    public Response health() {
        if (bus == null || bus.getState() == Bus.BusState.SHUTDOWN) {
            return Response.status(503).entity(Map.of(
                "status", "down",
                "reason", "cxf_bus_shutdown"
            )).build();
        }

        ServerRegistry registry = bus.getExtension(ServerRegistry.class);
        if (registry == null) {
            return Response.status(503).entity(Map.of(
                "status", "degraded",
                "reason", "server_registry_unavailable"
            )).build();
        }

        List<Server> servers = registry.getServers();
        List<Map<String, Object>> endpointDetails = new ArrayList<>();
        List<String> unavailable = new ArrayList<>();

        for (Server server : servers) {
            String address = server.getEndpoint().getEndpointInfo().getAddress();
            boolean started = server.isStarted();
            endpointDetails.add(Map.of("address", address, "started", started));
            if (!started) {
                unavailable.add(address);
            }
        }

        if (!unavailable.isEmpty()) {
            return Response.status(503).entity(Map.of(
                "status", "degraded",
                "reason", "endpoints_not_started",
                "unavailable", unavailable,
                "endpoints", endpointDetails
            )).build();
        }

        return Response.ok(Map.of(
            "status", "ok",
            "bus_state", bus.getState().name(),
            "endpoint_count", servers.size(),
            "endpoints", endpointDetails
        )).build();
    }
}

Register the health resource in your Spring Boot CXF configuration:

// CxfConfig.java
@Configuration
public class CxfConfig {

    @Bean
    public Server jaxRsServer(Bus bus) {
        JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
        factory.setBus(bus);
        factory.setAddress("/api");
        factory.setServiceBeans(List.of(
            new CxfHealthResource(bus),
            // ... your other resources
        ));
        return factory.create();
    }
}

WSDL Availability Check (Node.js Sidecar)

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

const app = express();

const WSDL_URLS = (process.env.CXF_WSDL_URLS || '').split(',').filter(Boolean);
const SERVICE_URL = process.env.CXF_SERVICE_URL || 'http://localhost:8080/services/MyService';

function fetchUrl(url) {
  return new Promise((resolve, reject) => {
    const client = url.startsWith('https') ? https : http;
    const req = client.get(url, { 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/cxf', async (req, res) => {
  const results = [];
  let allOk = true;

  // Check each WSDL endpoint
  for (const wsdlUrl of WSDL_URLS) {
    try {
      const { status, body } = await fetchUrl(wsdlUrl);
      const ok = status === 200 && body.includes('definitions');
      results.push({ url: wsdlUrl, ok, status });
      if (!ok) allOk = false;
    } catch (err) {
      results.push({ url: wsdlUrl, ok: false, error: err.message });
      allOk = false;
    }
  }

  // Check the main service URL for basic HTTP liveness
  try {
    const { status } = await fetchUrl(SERVICE_URL);
    results.push({ url: SERVICE_URL, ok: status < 500, status });
    if (status >= 500) allOk = false;
  } catch (err) {
    results.push({ url: SERVICE_URL, ok: false, error: err.message });
    allOk = false;
  }

  const httpStatus = allOk ? 200 : 503;
  return res.status(httpStatus).json({
    status: allOk ? 'ok' : 'degraded',
    endpoints: results,
  });
});

app.listen(3009);

Python Health Sidecar

# health_cxf.py
import os, requests
from flask import Flask, jsonify

app = Flask(__name__)

WSDL_URLS = [u for u in os.environ.get('CXF_WSDL_URLS', '').split(',') if u]
SERVICE_URL = os.environ.get('CXF_SERVICE_URL', 'http://localhost:8080/services/MyService')

@app.route('/health/cxf')
def health():
    results = []
    all_ok = True

    for url in WSDL_URLS:
        try:
            r = requests.get(url, timeout=5)
            ok = r.status_code == 200 and 'definitions' in r.text
            results.append({'url': url, 'ok': ok, 'status': r.status_code})
            if not ok:
                all_ok = False
        except Exception as e:
            results.append({'url': url, 'ok': False, 'error': str(e)})
            all_ok = False

    try:
        r = requests.get(SERVICE_URL, timeout=5)
        ok = r.status_code < 500
        results.append({'url': SERVICE_URL, 'ok': ok, 'status': r.status_code})
        if not ok:
            all_ok = False
    except Exception as e:
        results.append({'url': SERVICE_URL, 'ok': False, 'error': str(e)})
        all_ok = False

    code = 200 if all_ok else 503
    return jsonify({'status': 'ok' if all_ok else 'degraded', 'endpoints': results}), code

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

Step 2: Configure Vigilmon HTTP Monitor for CXF

  1. Log in to vigilmon.online and go to Monitors → New Monitor
  2. Choose HTTP / HTTPS
  3. Set the URL to your CXF health endpoint: https://your-app.example.com/api/health/cxf
  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 dedicated WSDL monitor for each SOAP service:

  • URL: https://your-app.example.com/services/PaymentService?wsdl
  • Expected: 200, body contains: definitions
  • Interval: 2 minutes
  • Alert channel: P2 Slack (WSDL unavailability breaks new integrations but not existing ones immediately)

Add a monitor for the REST API docs endpoint:

  • URL: https://your-app.example.com/services/api-docs (or OpenAPI endpoint)
  • Expected: 200
  • Interval: 5 minutes

Vigilmon's multi-region probe consensus filters transient network issues from genuine endpoint failures.


Step 3: Heartbeat Monitoring for CXF Business Operations

Endpoint liveness monitoring catches down services, but not functionally broken ones. A SOAP service can return HTTP 200 with a <faultcode>Server</faultcode> for every request — the endpoint is "up" but producing no successful results.

Vigilmon heartbeat monitors catch these: your service logic pings Vigilmon after each successful business operation.

Set Up the Heartbeat Monitor

  1. In Vigilmon, go to Monitors → New Monitor → Heartbeat
  2. Set the name: payment-service-cxf-processing
  3. Set the expected interval: 10 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 JAX-WS Service Implementation

// PaymentServiceImpl.java
import javax.jws.WebService;
import java.net.http.*;
import java.net.URI;

@WebService(endpointInterface = "com.example.PaymentService")
public class PaymentServiceImpl implements PaymentService {

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

    @Override
    public PaymentResponse processPayment(PaymentRequest request) {
        // Validate and process
        PaymentResponse response = paymentGateway.charge(request);

        if (response.isSuccess()) {
            // Signal Vigilmon that a payment was successfully processed
            pingHeartbeat();
        }

        return response;
    }

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

Wire It Into a CXF Interceptor for Cross-Cutting Heartbeat Pings

// VigilmonHeartbeatInterceptor.java
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import java.net.http.*;
import java.net.URI;

public class VigilmonHeartbeatInterceptor extends AbstractPhaseInterceptor<Message> {

    private final HttpClient http = HttpClient.newHttpClient();
    private final String heartbeatUrl;

    public VigilmonHeartbeatInterceptor(String heartbeatUrl) {
        super(Phase.SEND);
        this.heartbeatUrl = heartbeatUrl;
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        // This fires after a successful outbound response is assembled
        Boolean fault = message.getContent(Boolean.class);
        if (Boolean.TRUE.equals(fault)) return; // Don't ping on faults

        try {
            http.sendAsync(
                HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
                HttpResponse.BodyHandlers.discarding()
            );
        } catch (Exception ignored) {}
    }
}

Register the interceptor in Spring configuration:

@Bean
public Endpoint paymentEndpoint(Bus bus, PaymentServiceImpl impl) {
    EndpointImpl endpoint = new EndpointImpl(bus, impl);
    endpoint.publish("/PaymentService");
    endpoint.getOutInterceptors().add(new VigilmonHeartbeatInterceptor(
        System.getenv("VIGILMON_HEARTBEAT_URL")
    ));
    return endpoint;
}

Step 4: Alert Routing for CXF Failures

CXF failures range from a stalled interceptor (degraded but recoverable) to a Bus shutdown (all services unreachable). Route alerts by severity:

| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: /api/health/cxf bus + endpoint state | Slack + PagerDuty | P1 | | HTTP: WSDL availability ?wsdl | Slack + PagerDuty | P1 | | Heartbeat: business operation (payment, order, etc.) | Slack + PagerDuty | P1 | | HTTP: REST API docs / OpenAPI endpoint | Slack | P2 |

Set response time thresholds as early warning signals:

  • Alert at 2000ms for the health endpoint (elevated latency may indicate thread pool pressure)
  • Alert at 3000ms for WSDL endpoints (slow WSDL responses block client startup)
  • Alert at 5000ms for the main service endpoint under load

For production SOAP services, set up a status page in Vigilmon showing CXF endpoint health alongside WSDL availability — consumers integrating with your service can self-diagnose integration failures against this page.


Summary

CXF failures are protocol-layer failures — the JVM is up, the port is open, but SOAP faults are flowing or the WS-Security interceptor is rejecting every request. External monitoring catches these before they become incidents:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /api/health/cxf | CXF Bus state, registered endpoint count, endpoint started state | | HTTP monitor on ?wsdl endpoints | WSDL availability and schema validity | | HTTP monitor on REST API docs | OpenAPI / Swagger endpoint availability | | Heartbeat monitor on business operation | End-to-end successful service invocation liveness |

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