Apache Axiom is the StAX-based XML Object Model (DOOM) that forms the message processing foundation of Apache Axis2. Axiom's key design is deferred parsing: it reads the XML stream lazily, expanding OMNodes only when accessed rather than parsing the entire document on arrival. This makes it fast for large SOAP messages, but creates failure modes that fire far from their origin. A StAXParserException in a deferred parsing call surfaces in the business logic layer, not the transport layer. An OMException from a corrupted node tree returns as an HTTP 500 without a meaningful SOAP fault. An MTOM attachment boundary mismatch causes the entire message to fail during content extraction, not during receipt. The Axis2 endpoint is up, the request is accepted, and the failure is invisible until the caller times out.
Vigilmon gives you external visibility into Axiom XML processing health through HTTP probe monitoring and heartbeat monitors that detect when message parsing and content extraction stop succeeding. This tutorial covers both.
Why Axiom Needs External Monitoring
Axiom's lazy parsing model and MTOM attachment handling create failure modes that standard uptime checks miss:
- Deferred parsing exceptions: an
OMExceptionorXMLStreamExceptionfires when a node is first accessed, not when the SOAP message arrives — the endpoint accepts the message but fails internally - OMNode tree corruption: concurrent access to an OM tree that was not built thread-safely produces inconsistent node state, causing intermittent failures that don't correlate with traffic spikes
- MTOM attachment failures: an
Attachmentsobject that cannot locate a MIME boundary causes all attachment processing to fail, while the SOAP envelope processing appears normal - Builder state exhaustion: a
StAXOMBuilderthat reaches end-of-stream prematurely leaves the OM tree partially built; subsequent node access throwsOMException - Memory pressure from large documents: Axiom holds the entire OM tree in memory when fully expanded; large documents under heavy load cause GC pauses that appear as request timeouts
External monitoring with Vigilmon adds:
- Proactive alerting when the Axiom health endpoint returns non-200 or unexpected bodies
- XML parsing pipeline validation via a round-trip parse health check
- Heartbeat monitoring for successful end-to-end message processing
- Multi-region availability checking from outside your network perimeter
Step 1: Build an Axiom Health Endpoint
Axiom does not expose an HTTP health interface — you need to add one that performs a round-trip parse of a representative SOAP document to validate the entire StAX pipeline.
Java JAX-RS Health Resource (Axiom + Axis2 + Spring Boot)
// src/main/java/com/example/health/AxiomHealthResource.java
import org.apache.axiom.om.*;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.soap.*;
import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.xml.stream.*;
import java.io.*;
import java.util.*;
@Path("/health")
public class AxiomHealthResource {
private static final String TEST_SOAP =
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
" <soapenv:Header/>" +
" <soapenv:Body>" +
" <health:ping xmlns:health=\"http://example.com/health\">" +
" <health:timestamp>test-ts-value</health:timestamp>" +
" </health:ping>" +
" </soapenv:Body>" +
"</soapenv:Envelope>";
@GET
@Path("/axiom")
@Produces(MediaType.APPLICATION_JSON)
public Response health() {
List<String> issues = new ArrayList<>();
// Test 1: StAX round-trip parse of a SOAP envelope
try {
XMLStreamReader parser = XMLInputFactory.newInstance()
.createXMLStreamReader(new StringReader(TEST_SOAP));
StAXSOAPModelBuilder builder = new StAXSOAPModelBuilder(parser, null);
SOAPEnvelope envelope = builder.getSOAPEnvelope();
SOAPBody body = envelope.getBody();
OMElement firstChild = body.getFirstElement();
if (firstChild == null) {
issues.add("soap_body_empty_after_parse");
} else {
// Force deferred expansion of the subtree
String localName = firstChild.getLocalName();
OMElement ts = firstChild.getFirstChildWithName(
new javax.xml.namespace.QName("http://example.com/health", "timestamp"));
String tsText = ts != null ? ts.getText() : null;
if (!"test-ts-value".equals(tsText)) {
issues.add("soap_body_content_mismatch: got=" + tsText);
}
}
builder.close();
} catch (Exception e) {
issues.add("stax_parse_failed: " + e.getMessage());
}
// Test 2: OMFactory is accessible (metaclass infrastructure check)
try {
OMFactory factory = OMAbstractFactory.getOMFactory();
OMNamespace ns = factory.createOMNamespace("http://example.com/test", "t");
OMElement elem = factory.createOMElement("test", ns);
factory.createOMText(elem, "hello");
if (!"hello".equals(elem.getText())) {
issues.add("omfactory_text_roundtrip_failed");
}
} catch (Exception e) {
issues.add("omfactory_failed: " + e.getMessage());
}
if (!issues.isEmpty()) {
return Response.status(503).entity(Map.of(
"status", "degraded",
"issues", issues
)).build();
}
return Response.ok(Map.of(
"status", "ok",
"stax_parse", "ok",
"omfactory", "ok"
)).build();
}
}
Register the health resource in your Spring Boot CXF/Axis2 configuration:
// AxiomHealthConfig.java
@Configuration
public class AxiomHealthConfig {
@Bean
public Server jaxRsServer(Bus bus) {
JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
factory.setBus(bus);
factory.setAddress("/api");
factory.setServiceBeans(List.of(new AxiomHealthResource()));
return factory.create();
}
}
Node.js Health Sidecar
// health/axiom.js
const express = require('express');
const https = require('https');
const http = require('http');
const app = express();
const AXIS2_SERVICE_URL = process.env.AXIS2_SERVICE_URL || 'http://localhost:8080/axis2/services/DocumentService';
const AXIOM_HEALTH_URL = process.env.AXIOM_HEALTH_URL || 'http://localhost:8080/api/health/axiom';
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/axiom', async (req, res) => {
const results = [];
let allOk = true;
// Proxy the Java Axiom health resource
try {
const { status, body } = await fetchUrl(AXIOM_HEALTH_URL);
const parsed = JSON.parse(body);
const ok = status === 200 && parsed.status === 'ok';
results.push({ check: 'axiom_parse_pipeline', ok, status, detail: parsed });
if (!ok) allOk = false;
} catch (err) {
results.push({ check: 'axiom_parse_pipeline', ok: false, error: err.message });
allOk = false;
}
// Check the Axis2 service endpoint is reachable
try {
const { status } = await fetchUrl(AXIS2_SERVICE_URL + '?wsdl');
results.push({ check: 'axis2_service_wsdl', ok: status === 200, status });
if (status !== 200) allOk = false;
} catch (err) {
results.push({ check: 'axis2_service_wsdl', ok: false, error: err.message });
allOk = false;
}
return res.status(allOk ? 200 : 503).json({
status: allOk ? 'ok' : 'degraded',
checks: results,
});
});
app.listen(3012);
Python Health Sidecar
# health_axiom.py
import os, requests
from flask import Flask, jsonify
app = Flask(__name__)
AXIS2_SERVICE_URL = os.environ.get('AXIS2_SERVICE_URL', 'http://localhost:8080/axis2/services/DocumentService')
AXIOM_HEALTH_URL = os.environ.get('AXIOM_HEALTH_URL', 'http://localhost:8080/api/health/axiom')
@app.route('/health/axiom')
def health():
results = []
all_ok = True
try:
r = requests.get(AXIOM_HEALTH_URL, timeout=5)
parsed = r.json()
ok = r.status_code == 200 and parsed.get('status') == 'ok'
results.append({'check': 'axiom_parse_pipeline', 'ok': ok, 'status': r.status_code, 'detail': parsed})
if not ok:
all_ok = False
except Exception as e:
results.append({'check': 'axiom_parse_pipeline', 'ok': False, 'error': str(e)})
all_ok = False
try:
r = requests.get(AXIS2_SERVICE_URL + '?wsdl', timeout=5)
ok = r.status_code == 200
results.append({'check': 'axis2_service_wsdl', 'ok': ok, 'status': r.status_code})
if not ok:
all_ok = False
except Exception as e:
results.append({'check': 'axis2_service_wsdl', 'ok': False, 'error': str(e)})
all_ok = False
code = 200 if all_ok else 503
return jsonify({'status': 'ok' if all_ok else 'degraded', 'checks': results}), code
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3012)
Step 2: Configure Vigilmon HTTP Monitor for Axiom
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Axiom health endpoint:
https://your-app.example.com/api/health/axiom - Set the check interval to 1 minute
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
5000ms
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a secondary monitor for the Axis2 service WSDL:
- URL:
https://your-app.example.com/axis2/services/DocumentService?wsdl - Expected:
200, body contains:definitions - Interval: 2 minutes
- Alert channel: P1
Add a monitor for the Axis2 admin page if exposed:
- URL:
https://your-app.example.com/axis2/ - Expected:
200 - Interval: 5 minutes
- Alert channel: P2 Slack
Vigilmon's multi-region probe consensus filters StAX library transient errors from genuine parsing pipeline failures.
Step 3: Heartbeat Monitoring for Axiom Message Processing
Parse pipeline health confirms the StAX infrastructure is working, but not that large or complex real messages parse correctly under load. Axiom's deferred expansion means a message that passes the health check may still fail when a deeply nested OMNode is accessed with high cardinality child content.
Vigilmon heartbeat monitors catch these gaps: your service logic pings Vigilmon after each successfully parsed, expanded, and processed message.
Set Up the Heartbeat Monitor
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
axis2-axiom-message-processing - Set the expected interval: 10 minutes
- Set the grace period: 15 minutes
- Save — copy the heartbeat URL, e.g.
https://vigilmon.online/heartbeat/abc123xyz
Wire It Into an Axis2 Service Skeleton
// DocumentServiceSkeleton.java
import org.apache.axiom.om.OMElement;
import java.net.http.*;
import java.net.URI;
import java.util.Iterator;
public class DocumentServiceSkeleton {
private final HttpClient http = HttpClient.newHttpClient();
private final String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");
public OMElement processDocument(OMElement request) {
// Force full deferred expansion of the request tree
// This is where Axiom parse failures surface
OMElement payload = request.getFirstElement();
String documentId = null;
if (payload != null) {
OMElement idElem = payload.getFirstChildWithName(
new javax.xml.namespace.QName("http://example.com/doc", "documentId"));
documentId = idElem != null ? idElem.getText() : null;
}
OMElement result = documentService.process(documentId, payload);
if (result != null) {
// Full tree expanded and processed successfully
pingHeartbeat();
}
return result;
}
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 an Axis2 MessageReceiver for Cross-Cutting Pings
// VigilmonRawXMLMessageReceiver.java
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver;
import java.net.http.*;
import java.net.URI;
public class VigilmonRawXMLMessageReceiver extends RawXMLINOnlyMessageReceiver {
private final HttpClient http = HttpClient.newHttpClient();
private final String heartbeatUrl = System.getenv("VIGILMON_HEARTBEAT_URL");
@Override
public void invokeBusinessLogic(MessageContext msgContext) throws AxisFault {
super.invokeBusinessLogic(msgContext);
// If we get here, Axiom fully parsed and processed the message without exception
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 Axiom Failures
Axiom failures range from a single malformed document (recoverable per-message) to a StAX factory initialisation failure (all Axis2 message processing broken). Route alerts by severity:
| Monitor | Alert Channel | Priority |
|---|---|---|
| HTTP: /api/health/axiom StAX parse + OMFactory | Slack + PagerDuty | P1 |
| HTTP: Axis2 service WSDL availability | Slack + PagerDuty | P1 |
| Heartbeat: successful message parse and processing | Slack + PagerDuty | P1 |
| HTTP: Axis2 admin/index page | Slack | P2 |
Set response time thresholds as early warning signals:
- Alert at
2000msfor the Axiom health endpoint (slow StAX round-trip may indicate GC pressure or I/O saturation) - Alert at
3000msfor the service WSDL (slow WSDL delivery suggests Axis2 thread pool pressure) - Alert at
8000msfor actual service invocations (deferred OM tree expansion of large documents is CPU and memory intensive; elevated latency indicates saturation)
For services processing high-volume SOAP or MTOM messages, set up a status page in Vigilmon showing the Axiom parse pipeline health alongside message processing heartbeat state — SRE teams can use this page to distinguish StAX infrastructure failures from application-level processing errors.
Summary
Axiom failures are XML parsing layer failures — Axis2 is accepting connections, but deferred StAX parsing of message content is failing or producing corrupt OM trees. External monitoring catches these before they escalate to caller-visible errors:
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on /api/health/axiom | StAX round-trip parse, OMFactory infrastructure, deferred expansion |
| HTTP monitor on Axis2 service WSDL | Service deployment and WSDL availability |
| Heartbeat monitor on message processing | End-to-end Axiom parse, OM tree access, and business logic liveness |
| HTTP monitor on Axis2 admin page | Axis2 container availability |
Get started free at vigilmon.online — your first Axiom XML processing monitor is running in under two minutes.