Apache Any23 (Anything To Triples) is an open-source library and web service that extracts structured semantic data from web documents in RDF, Microdata, JSON-LD, RDFa, Microformats, and CSV formats for knowledge graph ingestion pipelines and linked data workflows. Any23 wraps a chain of MIME type detectors and format parsers behind a single HTTP extraction endpoint — when a parser in the chain hangs on a malformed HTML document with a pathological CSS selector, the extraction thread blocks indefinitely while Any23's embedded Jetty server queues subsequent requests that never execute, exhausting the thread pool without logging a single error; when the Tika MIME type detector misidentifies a large CSV as binary data, Any23 silently returns an empty RDF graph instead of 503 — the calling pipeline receives a valid but empty response and ingests nothing, corrupting downstream knowledge graph completeness; when the Any23 service is deployed behind a reverse proxy that strips the Content-Type header, every extraction request falls through to the fallback text/html parser which returns syntactically valid but semantically meaningless triples. These are data pipeline integrity failures disguised as successful HTTP responses.
Vigilmon gives you external visibility into Any23's extraction layer through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.
Why Any23 Needs External Monitoring
Any23's failure modes are subtle and data-corrupting:
- Parser thread stall on malformed documents: Any23's HTML parser uses a CSS-based extractor chain; a circular reference in a malformed HTML document's inline JSON-LD causes the JSONLDExtractor to loop indefinitely, holding a Jetty worker thread — under concurrent extraction load, all threads stall and Any23 stops processing requests with no JVM crash or error log entry
- Empty triple response on MIME detection failure: the Tika-based MIME detector occasionally misidentifies structured HTML documents with unusual encoding as binary/octet-stream; Any23 then skips all extractors and returns an empty NTriples response with HTTP 200 — the upstream pipeline sees a successful empty extraction, not an error
- Memory exhaustion on large RDF documents: Any23 holds the full parsed RDF graph in memory before writing the response; a 50MB RDF/XML document parsed in a constrained heap causes an OutOfMemoryError that kills the JVM while the reverse proxy returns 502 to callers with no error body
- Format converter queue backup: when Any23 is used as a batch converter via its CLI, large files fed faster than the converter threads can process cause the input queue to back up in memory — the CLI process appears running but conversion output stops being written
- Service restart on configuration reload: Any23's Jetty-based service restarts when
any23-core.propertiesis modified at runtime; during the restart window, all in-flight extraction requests receive TCP RST rather than a graceful 503, causing the upstream pipeline to treat the extraction as successful (no exception, just empty response body) - Extractor chain misconfiguration after upgrade: Any23 upgrades occasionally change the default extractor list in
any23-default.properties; a missinghtml-mf-hcardextractor entry after an upgrade causes all hCard extractions to silently return empty triples
External monitoring with Vigilmon adds:
- Proactive alerting when Any23's extraction endpoints return errors or slow responses
- Parser health monitoring independent of the HTTP layer status
- Heartbeat monitoring so you know when scheduled batch extraction and ETL pipeline jobs stop completing
- Multi-region probe consensus that filters transient Tika detection blips from genuine Any23 service failures
Step 1: Build an Any23 Health Endpoint
Any23 ships with a REST extraction endpoint at /any23/. Build a health wrapper that exercises the parser chain end-to-end.
Java Health Servlet
// health/Any23HealthServlet.java
package com.yourorg.any23.health;
import org.apache.any23.Any23;
import org.apache.any23.extractor.ExtractionContext;
import org.apache.any23.http.AcceptHeaderBuilder;
import org.apache.any23.source.StringDocumentSource;
import org.apache.any23.writer.NTriplesWriter;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class Any23HealthServlet extends HttpServlet {
private static final String PROBE_HTML =
"<html><head>" +
"<script type=\"application/ld+json\">" +
"{\"@context\":\"https://schema.org\",\"@type\":\"Organization\",\"name\":\"Probe\"}" +
"</script></head><body>Health probe</body></html>";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
Map<String, String> checks = new LinkedHashMap<>();
boolean anyFailed = false;
// Test JSON-LD extractor with a known-good document
try {
Any23 any23 = new Any23();
ByteArrayOutputStream out = new ByteArrayOutputStream();
any23.extract(
new StringDocumentSource(PROBE_HTML, "http://probe.local/", "text/html"),
new NTriplesWriter(out)
);
String triples = out.toString("UTF-8");
if (triples.contains("Organization") || triples.length() > 0) {
checks.put("jsonld_extractor", "ok");
} else {
checks.put("jsonld_extractor", "empty_response");
anyFailed = true;
}
} catch (Exception e) {
checks.put("jsonld_extractor", "down:" + e.getClass().getSimpleName());
anyFailed = true;
}
resp.setContentType("application/json");
resp.setStatus(anyFailed ? 503 : 200);
PrintWriter out = resp.getWriter();
out.write("{\"status\":\"" + (anyFailed ? "degraded" : "ok") + "\",\"checks\":{");
boolean first = true;
for (Map.Entry<String, String> e : checks.entrySet()) {
if (!first) out.write(",");
out.write("\"" + e.getKey() + "\":\"" + e.getValue() + "\"");
first = false;
}
out.write("}}");
}
}
Node.js Sidecar
// health/any23.js
const express = require('express');
const http = require('http');
const querystring = require('querystring');
const app = express();
const ANY23_HOST = process.env.ANY23_HOST || 'localhost';
const ANY23_PORT = parseInt(process.env.ANY23_PORT || '8080');
const PROBE_BODY = querystring.stringify({
url: 'data:text/html,<script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"Probe"}</script>'
});
function probeAny23() {
return new Promise((resolve, reject) => {
const req = http.request(
{
host: ANY23_HOST,
port: ANY23_PORT,
path: '/any23/',
method: 'POST',
timeout: 10000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'text/plain',
'Content-Length': Buffer.byteLength(PROBE_BODY),
},
},
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')); });
req.write(PROBE_BODY);
req.end();
});
}
app.get('/health/any23', async (req, res) => {
try {
const result = await probeAny23();
const up = result.status === 200;
const hasTriples = result.body && result.body.includes('<');
if (!up) {
return res.status(503).json({
status: 'down',
upstream_status: result.status,
});
}
if (!hasTriples) {
return res.status(503).json({
status: 'degraded',
detail: 'extractor returned empty response for known-good probe document',
});
}
return res.status(200).json({ status: 'ok' });
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
app.listen(3020);
Python Health Sidecar
# health_any23.py
import os, requests
from flask import Flask, jsonify
app = Flask(__name__)
ANY23_BASE = f"http://{os.environ.get('ANY23_HOST', 'localhost')}:{os.environ.get('ANY23_PORT', '8080')}"
PROBE_HTML = (
'<html><head><script type="application/ld+json">'
'{"@context":"https://schema.org","@type":"Organization","name":"Probe"}'
'</script></head><body>Health probe</body></html>'
)
@app.route('/health/any23')
def health():
checks = {}
any_failed = False
# Test extraction endpoint with a known-good JSON-LD document
try:
resp = requests.post(
f'{ANY23_BASE}/any23/',
data={'body': PROBE_HTML},
headers={'Accept': 'text/plain'},
timeout=10
)
if resp.status_code != 200:
checks['extraction'] = f'down:{resp.status_code}'
any_failed = True
elif not resp.text.strip():
checks['extraction'] = 'degraded:empty_response'
any_failed = True
else:
checks['extraction'] = f'ok:{len(resp.text.splitlines())}_triples'
except Exception as e:
checks['extraction'] = f'down:{e}'
any_failed = True
# Test service info endpoint
try:
resp = requests.get(f'{ANY23_BASE}/any23/info', timeout=5)
checks['info_endpoint'] = 'ok' if resp.status_code == 200 else f'down:{resp.status_code}'
except Exception as e:
checks['info_endpoint'] = f'down:{e}'
any_failed = True
status = 'degraded' if any_failed else 'ok'
return jsonify({'status': status, 'checks': checks}), 503 if any_failed else 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3020)
Step 2: Configure Vigilmon HTTP Monitor for Any23
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Any23 health endpoint:
https://any23.your-org.example.com/health/any23 - Set the check interval to 1 minute
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
10000ms
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a second monitor for the Any23 info endpoint to detect service restart cycles:
- URL:
https://any23.your-org.example.com/any23/info - Expected: status
200 - Interval: 2 minutes
- Alert channel: P1 pager — info endpoint down means the Jetty server is restarting or crashed
For high-throughput extraction deployments, add a third monitor targeting the extraction endpoint directly with a minimal probe:
- URL:
https://any23.your-org.example.com/any23/ - Method:
POSTwith bodyurl=https://schema.org/Organization - Expected: status
200 - Response time threshold:
15000ms - Interval: 5 minutes
- Alert channel: Slack — slow extraction indicates thread pool pressure
Vigilmon's multi-region probe consensus filters transient Tika initialization warmup delays from genuine Any23 extraction service failures.
Step 3: Heartbeat Monitoring for ETL and Batch Extraction Jobs
Health endpoint monitoring catches service availability failures, but not end-to-end batch extraction pipeline problems. Any23 can respond to the info endpoint while a batch ETL job that processes thousands of documents per hour has silently stalled on a pathological URL that holds a parser thread indefinitely.
Vigilmon heartbeat monitors catch these: your Any23 ETL and batch extraction jobs ping Vigilmon after each successful execution cycle.
Set Up Heartbeat Monitors
For batch extraction pipeline liveness:
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
any23-batch-extraction - Set the expected interval: 30 minutes
- Set the grace period: 60 minutes
- Save — copy the heartbeat URL
For knowledge graph ingestion pipeline liveness:
- Create another Heartbeat monitor named
any23-kg-ingestion - Set the expected interval: 1 hour
- Set the grace period: 2 hours
- Save — copy the heartbeat URL
Wire Heartbeats to Any23 Batch Jobs
Add heartbeat pings to your Any23 batch extraction driver:
// batch/Any23BatchDriver.java
package com.yourorg.any23.batch;
import org.apache.any23.Any23;
import org.apache.any23.source.HTTPDocumentSource;
import org.apache.any23.writer.NTriplesWriter;
import java.net.http.*;
import java.net.URI;
import java.util.List;
public class Any23BatchDriver {
private final HttpClient http = HttpClient.newHttpClient();
private final String heartbeatUrl = System.getenv("VIGILMON_BATCH_HEARTBEAT_URL");
private final Any23 any23 = new Any23();
public void processBatch(List<String> urls) throws Exception {
int processed = 0;
for (String url : urls) {
try {
any23.extract(
new HTTPDocumentSource(any23.getHTTPClient(), url),
new NTriplesWriter(System.out)
);
processed++;
} catch (Exception e) {
System.err.println("Extraction failed for " + url + ": " + e.getMessage());
}
}
System.out.println("Processed " + processed + "/" + urls.size() + " URLs");
pingHeartbeat(heartbeatUrl);
}
private void pingHeartbeat(String url) {
if (url == null) return;
try {
http.sendAsync(
HttpRequest.newBuilder(URI.create(url)).GET().build(),
HttpResponse.BodyHandlers.discarding()
);
} catch (Exception ignored) {}
}
}
For shell-based ETL pipelines:
#!/bin/bash
# any23-etl-cron.sh — run by cron for periodic batch extraction
/opt/any23/bin/any23-batch.sh --input /data/urls.txt --output /data/triples/
if [ $? -eq 0 ] && [ -n "$VIGILMON_KG_HEARTBEAT_URL" ]; then
curl -s "$VIGILMON_KG_HEARTBEAT_URL" > /dev/null 2>&1 || true
fi
Step 4: Alert Routing for Any23 Failures
Any23 failures range from empty triple responses that silently corrupt knowledge graph completeness to full service outages. Route alerts by severity:
| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: health endpoint (extraction probe) | Slack + PagerDuty | P1 | | HTTP: Any23 info endpoint | Slack + PagerDuty | P1 | | HTTP: extraction endpoint response time | Slack | P2 | | Heartbeat: batch extraction pipeline | Slack + PagerDuty | P1 | | Heartbeat: knowledge graph ingestion | Slack + PagerDuty | P1 |
Set response time thresholds as early warning signals:
- Alert at
8000msfor the health endpoint extraction probe (slow extraction indicates thread pool pressure or parser stall) - Alert at
1000msfor the info endpoint (slow info responses indicate Jetty restart or JVM GC pause) - Alert at
60 minutesgap in batch extraction heartbeat (stalled extraction means knowledge graph data goes stale) - Alert at
2 hoursgap in knowledge graph ingestion heartbeat during business hours
For production linked data pipelines, set up a status page in Vigilmon showing Any23 service availability and batch extraction liveness — this lets data engineering teams identify extraction failures before they surface as missing or stale knowledge graph entries.
Summary
Any23 failures present as empty triple responses or silent parser stalls before they escalate to full extraction service outages — upstream knowledge graph pipelines receive HTTP 200 with empty RDF bodies while Any23's thread pool is exhausted by stalled parsers. External monitoring catches these before they corrupt knowledge graph completeness:
| Monitor Type | What It Covers | |---|---| | HTTP monitor on health endpoint | End-to-end extraction chain availability with known-good probe | | HTTP monitor on info endpoint | Any23 Jetty service availability and restart detection | | HTTP monitor on extraction endpoint | Parser response time and thread pool health | | Heartbeat monitor (batch extraction) | End-to-end batch ETL pipeline execution success | | Heartbeat monitor (KG ingestion) | Knowledge graph ingestion pipeline liveness |
Get started free at vigilmon.online — your first Any23 semantic extraction monitor is running in under two minutes.