tutorial

How to Monitor ActiveMQ Artemis with Vigilmon

ActiveMQ Artemis powers many enterprise messaging workloads — but silent queue buildup, consumer drop-off, and cluster topology failures can stall your integration pipelines. Learn how to monitor broker uptime, queue depth, address memory, journal health, and cluster state with Vigilmon.

Apache ActiveMQ Artemis is the next-generation ActiveMQ broker — a complete rewrite featuring a high-performance journal, flexible addressing model, and multi-protocol support (AMQP, MQTT, STOMP, OpenWire, Core). It powers enterprise messaging for ERP integrations, microservice event buses, and IoT data ingestion pipelines.

But Artemis failures are rarely loud. A queue backed up beyond its address memory limit, a cluster node that lost its live/backup relationship, or a journal going into BLOCKED state can silently halt your consumers while the broker process itself stays up. Vigilmon gives you external visibility into Artemis health through its built-in HTTP management API and heartbeat monitors for consumer applications.


Why ActiveMQ Artemis Needs External Monitoring

Artemis ships with a management console (Hawtio) and exposes JMX MBeans — but these require active watching. External monitoring with Vigilmon adds:

  • Proactive alerting when the broker management API returns errors or the broker stops responding
  • Queue depth threshold checks before addresses exhaust their configured memory limit
  • Journal health monitoring so you catch BLOCKED or FULL journal states before they cause message loss
  • Cluster topology checks to detect live/backup pair failures before a failover completes
  • Heartbeat monitoring for consumer applications that must prove they are actively processing

Step 1: Enable the Artemis Management REST API

Artemis ships with an embedded Jolokia REST API for management access. Verify it is enabled in broker.xml:

<!-- broker.xml -->
<management-context>
  <enabled>true</enabled>
  <jolokia-rest-api-endpoint>http://localhost:8161/console/jolokia</jolokia-rest-api-endpoint>
</management-context>

The management console is typically at http://<host>:8161/console. Jolokia endpoints follow the pattern:

http://<host>:8161/console/jolokia/read/<MBean>/<attribute>

Test that the API is accessible:

# Check broker uptime (seconds since start)
curl -u admin:admin \
  "http://localhost:8161/console/jolokia/read/org.apache.activemq.artemis:broker=\"0.0.0.0\"/Started"

# Expected: {"value": true, "status": 200}

Create a dedicated read-only management user:

<!-- artemis-users.properties -->
vigilmon-monitor=your-strong-password

<!-- artemis-roles.properties -->
monitoring=vigilmon-monitor
<!-- broker.xml: grant monitoring role read access -->
<security-setting match="#">
  <permission type="browse" roles="monitoring"/>
  <permission type="manage" roles="admin"/>
</security-setting>

Step 2: Build an Artemis Health Endpoint

Write a lightweight health sidecar that queries Artemis via Jolokia and exposes a clean /health endpoint for Vigilmon:

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

app = Flask(__name__)

JOLOKIA = os.environ.get('ARTEMIS_JOLOKIA_URL',
                         'http://localhost:8161/console/jolokia')
JOLOKIA_USER = os.environ.get('ARTEMIS_USER', 'admin')
JOLOKIA_PASS = os.environ.get('ARTEMIS_PASS', 'admin')
BROKER_NAME  = os.environ.get('ARTEMIS_BROKER', '0.0.0.0')
QUEUE_DEPTH_THRESHOLD = int(os.environ.get('ARTEMIS_QUEUE_DEPTH_THRESHOLD', '10000'))
ADDRESS_MEMORY_THRESHOLD = float(os.environ.get('ARTEMIS_ADDRESS_MEMORY_THRESHOLD', '0.85'))

def jolokia_read(mbean, attribute):
    url = f"{JOLOKIA}/read/{mbean}/{attribute}"
    r = requests.get(url, auth=(JOLOKIA_USER, JOLOKIA_PASS), timeout=5)
    r.raise_for_status()
    data = r.json()
    if data.get('status') != 200:
        raise RuntimeError(f"Jolokia error: {data}")
    return data['value']

@app.route('/health/artemis')
def health():
    issues = []

    # Broker uptime / started
    started = jolokia_read(
        f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'Started')
    if not started:
        return jsonify(status='down', reason='broker_not_started'), 503

    # Global address memory usage
    global_max = jolokia_read(
        f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'GlobalMaxSize')
    address_memory = jolokia_read(
        f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'AddressMemoryUsage')
    if global_max and global_max > 0:
        fill = address_memory / global_max
        if fill >= ADDRESS_MEMORY_THRESHOLD:
            issues.append(f'address_memory_fill_{int(fill*100)}pct')

    # Journal status
    journal_type = jolokia_read(
        f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'JournalType')
    paging_directory = jolokia_read(
        f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'PagingDirectory')

    if issues:
        return jsonify(status='degraded', issues=issues), 503

    return jsonify(
        status='ok',
        broker=BROKER_NAME,
        address_memory_pct=round(address_memory / global_max * 100, 1) if global_max else 0,
        journal_type=journal_type,
    ), 200

@app.route('/health/artemis/queues')
def queues():
    issues = []

    # List all queues and check message count + consumer count
    queues_data = jolokia_read(
        f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'QueueNames')

    queue_stats = []
    for queue_name in (queues_data or []):
        mbean = (f'org.apache.activemq.artemis:broker="{BROKER_NAME}",'
                 f'component=addresses,address="{queue_name}",'
                 f'subcomponent=queues,routing-type="anycast",queue="{queue_name}"')
        try:
            msg_count     = jolokia_read(mbean, 'MessageCount')
            consumer_count = jolokia_read(mbean, 'ConsumerCount')
            queue_stats.append({
                'queue': queue_name,
                'messages': msg_count,
                'consumers': consumer_count,
            })
            if msg_count > QUEUE_DEPTH_THRESHOLD:
                issues.append(f'queue_{queue_name}_depth_{msg_count}')
            if consumer_count == 0 and msg_count > 0:
                issues.append(f'queue_{queue_name}_no_consumers')
        except Exception:
            pass

    if issues:
        return jsonify(status='degraded', issues=issues, queues=queue_stats), 503
    return jsonify(status='ok', queues=queue_stats), 200

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

Step 3: Cluster Topology Monitoring

For clustered Artemis deployments, add a dedicated cluster health check:

@app.route('/health/artemis/cluster')
def cluster():
    try:
        # Live/backup pair state
        is_backup = jolokia_read(
            f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'Backup')
        is_live_override = jolokia_read(
            f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'ReplicaSync')
        ha_policy = jolokia_read(
            f'org.apache.activemq.artemis:broker="{BROKER_NAME}"', 'HAPolicy')

        return jsonify(
            status='ok',
            is_backup=is_backup,
            replica_sync=is_live_override,
            ha_policy=ha_policy,
        ), 200
    except Exception as e:
        return jsonify(status='unknown', error=str(e)), 200

For a minimal cluster check that Vigilmon can probe without a sidecar, use the Artemis management REST console directly:

# Verify the live node is serving
curl -f -u admin:password \
  "http://your-broker:8161/console/jolokia/read/org.apache.activemq.artemis:broker=%220.0.0.0%22/Started"

Point a Vigilmon HTTP monitor at this URL with basic auth — any non-200 response or a false value indicates the broker is down or in failover.


Step 4: Configure Vigilmon HTTP Monitors

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

Add monitors for queue and cluster health:

| Monitor URL | Purpose | Interval | |---|---|---| | /health/artemis | Broker uptime, address memory | 1 min | | /health/artemis/queues | Queue depth, consumer count | 1 min | | /health/artemis/cluster | Live/backup HA pair sync | 1 min |


Step 5: Heartbeat Monitoring for Artemis Consumer Applications

A healthy broker does not guarantee healthy consumers. A consumer stuck in an exception retry loop, blocked on a downstream service, or disconnected after a failover may reconnect to the broker but process zero messages.

Vigilmon heartbeat monitors detect this: your consumer sends a ping after each successful batch. If pings stop arriving, Vigilmon fires an alert.

Set Up the Heartbeat Monitor

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

Wire It Into Your Consumer (Java — Artemis Core Client)

// ArtemisConsumer.java
import org.apache.activemq.artemis.api.core.client.*;
import java.net.http.*;
import java.net.URI;
import java.util.concurrent.atomic.AtomicLong;

public class ArtemisConsumer {
    private static final String VIGILMON_HEARTBEAT =
        System.getenv("VIGILMON_HEARTBEAT_URL");
    private static final long HEARTBEAT_EVERY_N = 50;
    private static AtomicLong count = new AtomicLong(0);
    private static final HttpClient http = HttpClient.newHttpClient();

    public static void main(String[] args) throws Exception {
        ServerLocator locator = ActiveMQClient.createServerLocatorWithoutHA(
            "tcp://" + System.getenv("ARTEMIS_HOST") + ":61616");
        locator.setReconnectAttempts(-1);  // auto-reconnect after failover

        ClientSessionFactory factory = locator.createSessionFactory();
        ClientSession session = factory.createSession(
            System.getenv("ARTEMIS_USER"), System.getenv("ARTEMIS_PASS"),
            false, true, true, false, 1
        );

        ClientConsumer consumer = session.createConsumer(
            System.getenv("ARTEMIS_QUEUE"));
        session.start();

        while (true) {
            ClientMessage msg = consumer.receive(5000);
            if (msg != null) {
                processMessage(msg);
                msg.acknowledge();

                if (count.incrementAndGet() % HEARTBEAT_EVERY_N == 0) {
                    pingVigilmon();
                }
            }
        }
    }

    private static void pingVigilmon() {
        try {
            http.send(
                HttpRequest.newBuilder(URI.create(VIGILMON_HEARTBEAT)).GET().build(),
                HttpResponse.BodyHandlers.discarding()
            );
        } catch (Exception ignored) {}
    }

    private static void processMessage(ClientMessage msg) {
        // your processing logic
    }
}

Python Consumer (STOMP over Artemis)

# consumer.py
import stomp, requests, os, time

VIGILMON_URL = os.environ['VIGILMON_HEARTBEAT_URL']
last_heartbeat = time.time()
HEARTBEAT_INTERVAL = 60

class ArtemisListener(stomp.ConnectionListener):
    def on_message(self, frame):
        global last_heartbeat
        process(frame.body)
        if time.time() - last_heartbeat > HEARTBEAT_INTERVAL:
            try:
                requests.get(VIGILMON_URL, timeout=3)
            except Exception:
                pass
            last_heartbeat = time.time()

conn = stomp.Connection([(os.environ['ARTEMIS_HOST'], 61613)])
conn.set_listener('', ArtemisListener())
conn.connect(os.environ['ARTEMIS_USER'], os.environ['ARTEMIS_PASS'], wait=True)
conn.subscribe(destination=f"/queue/{os.environ['ARTEMIS_QUEUE']}",
               id=1, ack='client-individual')
conn.start()

Step 6: Alert Routing

| Monitor | Alert Channel | Priority | |---|---|---| | Broker health /health/artemis | Slack + PagerDuty | P1 | | Cluster HA /health/artemis/cluster | Slack + PagerDuty | P1 | | Queue depth /health/artemis/queues | Slack | P2 | | Heartbeat: consumer apps | Slack + email | P2 |

Set response time thresholds:

  • Alert at 2000ms for broker health (Jolokia slowness signals GC pressure or journal contention)
  • Alert at 4000ms for queue scans (large queue lists take longer to enumerate)

For critical queues, also monitor the consumer count attribute directly — a zero-consumer queue with pending messages is often worse than high queue depth, since depth might be intentional batching but zero consumers means nothing is processing.


Summary

ActiveMQ Artemis failures are quiet until they cascade. External monitoring with Vigilmon catches them before they reach your consumers:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /health/artemis | Broker uptime, address memory usage | | HTTP monitor on /health/artemis/queues | Queue depth, consumer bind counts | | HTTP monitor on /health/artemis/cluster | Live/backup HA pair state | | Heartbeat monitor | Consumer application liveness |

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