How to Monitor Apache MetaModel Applications (Free, Multi-Region)
Apache MetaModel gives you a unified API to query data from JDBC databases, CSV files, JSON documents, spreadsheets, and dozens of other sources. But when a data pipeline silently fails — a broken JDBC connection, a missing CSV file, or an empty result set — nobody gets alerted unless you set up external monitoring.
By the end of this guide you'll have external uptime monitoring, heartbeat checks for your MetaModel batch jobs, and instant alerts — all running on the free tier.
Why MetaModel applications go dark silently
MetaModel apps have two failure modes that slip through:
Data source connectivity failures — your JDBC DataSource loses connectivity, a CSV path changes, or a remote Elasticsearch cluster becomes unreachable. MetaModel will throw at query time, but unless something is watching, users get errors or stale data with no alert fired.
Silent batch job failures — a scheduled MetaModel ETL job throws a MetaModelException or a QueryParserException. The thread pool catches it and the next run never triggers. Your reporting pipeline is broken, and the dashboard just shows yesterday's numbers forever.
Both are solvable with one external monitoring tool checking from outside your infrastructure.
Step 1: Expose a health endpoint
MetaModel doesn't include a built-in health endpoint, so add a lightweight servlet or controller that validates your primary data sources at startup and on demand.
With Spring Boot:
import org.apache.metamodel.DataContext;
import org.apache.metamodel.DataContextFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
import java.util.Map;
@RestController
public class HealthController {
private final DataSource dataSource;
public HealthController(DataSource dataSource) {
this.dataSource = dataSource;
}
@GetMapping("/health")
public Map<String, String> health() {
try {
DataContext dc = DataContextFactory.createJdbcDataContext(dataSource);
// A lightweight schema fetch proves connectivity
dc.getDefaultSchema();
return Map.of("status", "UP", "metamodel", "connected");
} catch (Exception e) {
throw new RuntimeException("MetaModel data source unavailable: " + e.getMessage());
}
}
}
For a standalone (non-Spring) application, wire a simple HTTP server:
import com.sun.net.httpserver.HttpServer;
import org.apache.metamodel.DataContextFactory;
import java.net.InetSocketAddress;
public class HealthServer {
public static void start(DataSource ds) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(8081), 0);
server.createContext("/health", exchange -> {
try {
DataContextFactory.createJdbcDataContext(ds).getDefaultSchema();
byte[] body = "{\"status\":\"UP\"}".getBytes();
exchange.sendResponseHeaders(200, body.length);
exchange.getResponseBody().write(body);
} catch (Exception e) {
byte[] body = "{\"status\":\"DOWN\"}".getBytes();
exchange.sendResponseHeaders(503, body.length);
exchange.getResponseBody().write(body);
} finally {
exchange.close();
}
});
server.start();
}
}
Verify locally:
curl http://localhost:8081/health
# {"status":"UP","metamodel":"connected"}
Step 2: Test multiple data source types
If your app queries several backends, surface each one in the health response:
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health(
DataSource jdbcDs,
Path csvPath) {
Map<String, Object> details = new LinkedHashMap<>();
boolean allUp = true;
// Check JDBC
try {
DataContextFactory.createJdbcDataContext(jdbcDs).getDefaultSchema();
details.put("jdbc", "UP");
} catch (Exception e) {
details.put("jdbc", "DOWN: " + e.getMessage());
allUp = false;
}
// Check CSV file availability
try {
DataContextFactory.createCsvDataContext(csvPath.toFile(), ',', '"');
details.put("csv", "UP");
} catch (Exception e) {
details.put("csv", "DOWN: " + e.getMessage());
allUp = false;
}
details.put("status", allUp ? "UP" : "DOWN");
return ResponseEntity
.status(allUp ? 200 : 503)
.body(details);
}
When any source is down, the endpoint returns HTTP 503, which Vigilmon treats as an incident.
Step 3: Set up external monitoring with Vigilmon
With /health live, point Vigilmon at it:
- Sign up at vigilmon.online — free tier, no credit card
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set check interval (5 minutes on free tier)
- Save
Vigilmon probes from multiple regions simultaneously. If it receives a non-2xx response or a timeout, it opens an incident and alerts you before your users notice.
Recommended monitors for a MetaModel app:
| Endpoint | What it catches |
|---|---|
| /health | JDBC, CSV, or remote source down |
| /api/reports/latest | End-to-end query pipeline broken |
| / | Frontend or API gateway down |
Step 4: Heartbeat monitoring for scheduled MetaModel jobs
HTTP checks don't catch silent ETL failures. Heartbeat monitoring does.
The pattern: your batch job pings a unique URL at the end of each successful run. If Vigilmon stops receiving the ping within the expected window, it fires an alert.
import org.apache.metamodel.DataContext;
import org.apache.metamodel.DataContextFactory;
import org.apache.metamodel.query.Query;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class NightlyEtlJob {
private static final String HEARTBEAT_URL =
System.getenv("HEARTBEAT_ETL_URL"); // set in env
private final DataSource source;
private final DataSource target;
public NightlyEtlJob(DataSource source, DataSource target) {
this.source = source;
this.target = target;
}
public void run() throws Exception {
DataContext sourceCtx = DataContextFactory.createJdbcDataContext(source);
DataContext targetCtx = DataContextFactory.createJdbcDataContext(target);
// Execute MetaModel query
Query q = sourceCtx.query()
.from(sourceCtx.getDefaultSchema().getTableByName("orders"))
.select("id", "total", "created_at")
.where("processed").eq(false)
.toQuery();
var rows = sourceCtx.executeQuery(q);
// ... transform and insert into target ...
// Only ping heartbeat on clean success
pingHeartbeat();
}
private void pingHeartbeat() {
if (HEARTBEAT_URL == null || HEARTBEAT_URL.isBlank()) return;
try {
HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create(HEARTBEAT_URL)).GET().build(),
HttpResponse.BodyHandlers.discarding()
);
} catch (Exception ignored) {}
}
}
In Vigilmon:
- Click New Monitor → Heartbeat
- Set the expected interval (e.g. 25 hours for a daily job)
- Copy the unique ping URL
- Set
HEARTBEAT_ETL_URLin your environment
If the ETL throws at any step before pingHeartbeat(), the ping is never sent and Vigilmon alerts you within one missed interval.
Step 5: Webhook alerts and badge embed
Slack/Discord alerts:
- In Vigilmon go to Notifications → New Channel
- Choose Slack or Discord, paste your webhook URL
- Enable it on your monitors
You'll get instant alerts when a monitor goes down and a recovery notification when it comes back up.
Add an uptime badge to your README:
[](https://vigilmon.online?utm_source=devto&utm_medium=article&utm_campaign=metamodel-tutorial)
What you've built
| What | How |
|---|---|
| External health checks | /health endpoint + Vigilmon HTTP monitor |
| Multi-source connectivity | JDBC, CSV checks in one response |
| ETL job monitoring | Heartbeat ping at end of each run |
| Instant alerts | Slack/Discord notifications |
| README status badge | Vigilmon badge embed |
The entire setup runs on the free tier in under 30 minutes. Your next silent MetaModel failure will fire an alert before your users see stale data.
Get started free at vigilmon.online — monitors running in under a minute, no credit card required.