Apache ActiveMQ Artemis transport

com.babelqueue:babelqueue-artemis is an Apache ActiveMQ Artemis transport on the Java core, built on JMS (Jakarta Messaging 3.x). It sends the canonical envelope as the message body with the §7 projection, and consumes by routing each message to a handler by URN — so a message it produces is consumed by any other BabelQueue SDK, and vice-versa. Artemis speaks AMQP 1.0, so the other SDKs interoperate over the wire even though Java uses JMS.

Install

Maven:

<dependency>
    <groupId>com.babelqueue</groupId>
    <artifactId>babelqueue-artemis</artifactId>
    <version>1.0.0</version>
</dependency>

Requirements: Java 17+. It pulls babelqueue-core transitively; bring your Artemis JMS client (org.apache.activemq:artemis-jakarta-client), which supplies both the jakarta.jms API and the broker connection.

Produce

import com.babelqueue.artemis.ArtemisPublisher;
import jakarta.jms.*;
import org.apache.activemq.artemis.jms.client.ActiveMQJMSConnectionFactory;
import java.util.Map;

ConnectionFactory factory = new ActiveMQJMSConnectionFactory("tcp://localhost:61616");
try (JMSContext ctx = factory.createContext("user", "pass")) {
    Session session = ctx.createSession(Session.CLIENT_ACKNOWLEDGE);
    MessageProducer producer = session.createProducer(session.createQueue("orders"));

    String id = ArtemisPublisher.create(session, producer)
        .publish("urn:babel:orders:created", Map.of("order_id", 1042L));
}

publish(urn, data) returns the message meta.id; overloads add a traceId and a relative Duration delay (native JMS 2.0 setDeliveryDelay).

Consume

import com.babelqueue.artemis.*;
import jakarta.jms.*;

Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(session.createQueue("orders"));
MessageProducer dlqProducer = session.createProducer(null); // anonymous, for <queue>.dlq

ArtemisConsumer worker = ArtemisConsumer.builder(consumer, session)
    .handler("urn:babel:orders:created", (envelope, message) -> {
        // envelope.data(), envelope.traceId(), envelope.attempts() ...
    })
    .deadLetterQueue(dlqProducer, "orders.dlq")
    .maxTries(3)
    .build();

connection.start();
worker.run(() -> true); // receive → process → acknowledge

A successful handler acknowledges the message. A throwing handler leaves it unacknowledged and recovers the session, so the broker redelivers it and bumps JMSXDeliveryCount; once maxTries is reached the envelope goes to <queue>.dlq with a dead_letter block. The consumer routes on JMSType. Unknown-URN strategy is one of fail / delete / release / dead_letter.

One message per poll() keeps the session-wide acknowledge() / recover() correct — a JMS session is single-threaded, so run one ArtemisConsumer per thread.

Contract mapping (§7)

Envelope Apache ActiveMQ Artemis (JMS)
body message body (TextMessage, byte-identical across SDKs)
job (URN) JMSType (consumer routes on this)
trace_id JMSCorrelationID
meta.id JMSMessageID (broker-set for JMS; body is authoritative)
meta.schema_version property bq_schema_version
meta.lang property bq_source_lang
meta.created_at JMSTimestamp (Unix ms)
attempts max(body, JMSXDeliveryCount − 1) (broker counter is 1-based)
reserve / ack receive → process → acknowledge() (CLIENT_ACKNOWLEDGE)
retry / delay recover() redelivery · native setDeliveryDelay
dead-letter <queue>.dlq + dead_letter block (alongside the native DLA)

The JMS interfaces are mocked with Mockito — no Artemis, no network. The envelope is unchanged (schema_version stays 1); Apache ActiveMQ Artemis is purely additive.