13. Juni 2025

Apache ActiveMQ Artemis JMS Bridge

Nachrichten zwischen unterschiedlichen Message-Brokern zuverlässig austauschen – genau dafür ist die JMS Bridge da. In diesem Beitrag zeigt Christoph, wie sich mit Apache ActiveMQ Artemis und einer schlanken Spring Boot Anwendung verschiedenste Systeme verbinden lassen. Erhalte einen praxisnahen Überblick über die Konfiguration, Einsatzszenarien und Vorteile, egal ob IBM MQ, AMQ Broker oder ein anderer JMS Broker.

Software Development & Architecture
Active MQ Beitragsbild Blogpost

Apache ActiveMQ ist ein weit verbreiteter Open Source Message Broker für verschiedene Protokolle, basierend auf Java. Dabei unterstützt die JMS-Infrastruktur gängige Sprachen und Plattformen, um Clients in JavaScript, C, C++, Python, .NET, Java und mehr zu entwickeln.

Apache unterscheidet zwischen der klassischen, herkömmlichen Lösung ActiveMQ Classic und der hochperformanten, neuen Lösung ActiveMQ Artemis für die nächste Generation von Messaging. Um zwischen verschiedenen Systemen und Plattformen Messages austauschen zu können, wird eine weitere Komponente notwendig: die JMS Bridge. Diese konsumiert Messages von einer Source Destination und sendet diese an eine bestimmte Target-Destination, welche typischerweise auf einem anderen Server liegen. Apache ActiveMQ Artemis Bridges unterstützen dabei Filter-, Transformations- oder Routing-Möglichkeiten.

In diesem Blogpost wollen wir uns die Möglichkeiten der JMS Bridge als Standalone Application ansehen. Die JMS Bridge ist nicht zu verwechseln mit der Core Bridge, welche es erlaubt, innerhalb von ActiveMQ Brokern Messages auszutauschen. Die Core Bridge und das Core-Protokoll sind auf hohe Performance ausgelegt und optimiert. Die JMS Bridge hingegen ist auf Interoperabilität ausgelegt und benötigt spezifische Implementierungen für unterschiedliche Message Broker-Hersteller und Plattformen.

Application Integration Pattern

Grundsätzlich lassen sich vier verschiedene Szenarien unterscheiden, wie zwei Applikationen über eine JMS Bridge integriert werden können:

1) Source und Target System stellen beide eine Destination (Queue), typischerweise von einem Message-Broker, zur Verfügung. Die JMS Bridge liest aus der Source Destination und schreibt in die Target Destination.

2) Das Source System stellt eine Destination (Queue), typischerweise von einem Message-Broker, zur Verfügung,. Die Artemis-Infrastruktur stellt die Target Destination bereit, und ein JMS-Client auf dem Zielsystem kann die Messages auslesen.

3) Die JMS Clients von Source und Target System verwenden die Destinations, welche von der Artemis Infrastruktur zur Verfügung gestellt werden.

4) Der JMS Client des Source System schreibt in die Destination, welche die Artemis Infrastruktur zur Verfügung stellt. Die JMS Bridge leitet die Messages an die Target-Destination weiter, typischerweise von einem Message-Broker.

Blogpost JMS Bridge Visual Application-Integration-Pattern-BD.drawio

Je nach Integration Pattern muss auf der Artemis Infrastruktur neben der JMS Bridge auch ein Message-Broker betrieben werden, welcher allenfalls die Destination(s) zur Verfügung stellt.

JMS Bridge Application

Um die Anbindung an verschiedene Systeme und Plattformen zu ermöglichen, müssen Entwickler:innen spezifische Implementierungen von Connection Factories und Destinations bereitstellen. JMS definiert dabei den Standard, den die unterschiedlichen Message-Broker-Hersteller implementieren und unterstützen.

In diesem Beispiel implementieren wir eine Spring Boot Application, welche verschiedene Lösungen von Connection Factories sowie deren Konfiguration beinhaltet. Das mag auf den ersten Blick als Overkill erscheinen, der Codieraufwand hält sich jedoch in Grenzen und der Betrieb der Spring Boot Application bietet all die angenehmen Vorteile von einfachen und bekannten Prozessen wie Build, Deploy & Run inkl. Logging, Monitoring etc.

Starten wir mit der Connection Factory für Artemis; dazu sind folgende Properties für die Protokolle Core und AMQP notwendig:

record ArtemisConnectionFactoryProperties(
String brokerUrl, String username, String password, String queue) {
}

Nutzer:innen können diese Konfiguration entweder in der Konfigurationsdatei definieren oder beim Instanzieren der Connection Factory anwenden. Im Fall von Artemis Core:

public ConnectionFactory createConnectionFactory() {
    return new org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory(
        getBrokerUrl(), getUsername(), getPassword());
  }

Ähnlich sieht die Instanzierung von Artemis AMQP aus – mit einer spezifischen JMS ConnectionFactory für AMQP (auch QPID Client):

public ConnectionFactory createConnectionFactory() {
    return new org.apache.qpid.jms.JmsConnectionFactory(
        getUsername(), getPassword(), getBrokerUrl());
  }

Ein IBM MQ Client verlangt etwas andere Parameter, die man im Konstruktor der Factory-Klasse setzt:

public IbmMqConnectionFactory(Map<String, Object> config) {
    this.host = (String) config.get("host");
    this.port = (int) config.get("port");
    this.queueManager = (String) config.get("queue-manager");
    this.channel = (String) config.get("channel");
    this.queue = (String) config.get("queue");
    this.username = (String) config.get("username");
    this.password = (String) config.get("password");
  }

Damit lässt sich die Connection Factory für den IBM MQ Client instanziieren und konfigurieren:

public ConnectionFactory createConnectionFactory() throws JMSException {
    MQConnectionFactory factory = new MQConnectionFactory();
    factory.setHostName(host);
    factory.setPort(port);
    factory.setQueueManager(queueManager);
    factory.setChannel(channel);
    factory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
    return factory;
  }

In allen aufgeführten Beispielen initialisiert man jeweils nur eine Connection Factory. Die eigentliche Connection sowie die Destination (Source oder Target) entstehen erst im nächsten Schritt – bei der Instanzierung der JMS Bridge.

JMSBridge jmsBridge = new JMSBridgeImpl();
  jmsBridge.setBridgeName(jmsBridgeProperties.name());
  jmsBridge.setSourceConnectionFactoryFactory(() -> sourceConnectionFactory);
  jmsBridge.setSourceDestinationFactory(() -> sourceQueue);
  jmsBridge.setTargetConnectionFactoryFactory(() -> targetConnectionFactory);
  jmsBridge.setTargetDestinationFactory(() -> targetQueue);
  jmsBridge.setSelector(jmsBridgeProperties.selector());
  jmsBridge.setFailureRetryInterval(jmsBridgeProperties.getFailureRetryIntervalOrDefault());
  jmsBridge.setMaxRetries(jmsBridgeProperties.getMaxRetriesOrDefault());
  jmsBridge.setQualityOfServiceMode(jmsBridgeProperties.getQualityOfServiceModeOrDefault());
  jmsBridge.setMaxBatchSize(jmsBridgeProperties.getMaxBatchSizeOrDefault());
  jmsBridge.setMaxBatchTime(jmsBridgeProperties.getMaxBatchTimeOrDefault());
  jmsBridge.setSubscriptionName(jmsBridgeProperties.subName());
  jmsBridge.setClientID(jmsBridgeProperties.clientId());
  jmsBridge.setAddMessageIDInHeader(jmsBridgeProperties.getAddMessageIdInHeaderOrDefault());

Nebst den spezifischen Connection Factories für die Anbindung von Source und Target Destination sind eine Reihe von JMS Bridge Parametern notwendig, welche das Verhalten der JMS Bridge beeinflussen. Die JMS Bridge kann z.B. so konfiguriert werden, dass sie nach einem Verbindungsunterbruch zum Source oder Target System automatisch die Verbindung wieder aufbaut. Zudem können Authentication und / oder Verschlüsselung für die sichere und geschützte Übertragung von Messages eingesetzt werden.

Nachdem wir alle JMS Bridges erfolgreich instanziert haben, starten wir sie explizit. Beim Shutdown der Applikation stoppen wir sie kontrolliert. Spring Boot eignet sich dafür besonders gut: Über einen Application Lifecycle Service lassen sich Start (@PostConstruct) und Stopp (@PreDestroy) der JMS Bridges gezielt steuern.

Ziel ist es, anhand einer Liste von JMS Bridge-Konfigurationen die jeweiligen Connection Factories und Destinations aufzusetzen. Damit ist es zu einem späteren Zeitpunkt sehr einfach möglich, weitere JMS Bridges zu konfigurieren oder bestehende anzupassen. D.h. nach der erfolgreichen Einführung der Applikation kann diese an das Business Team übergeben werden, welches selbständig die Konfiguration warten kann.

Beispiel einer möglichen JMS Bridge-Konfiguration:

app:
  jmsBridges:
    - name: JMS_SOURCE_TARGET_TEST
      failure-retry-interval: 600
      max-retries: 10
      quality-of-service-mode: DUPLICATES_OK
      add-message-id-in-header: true
      source:
        type: IBM_MQ_CLIENT
        config:
          host: 192.193.194.195
          port: 1414
          queue-manager: QMPUZZLE
          channel: COM.PUZZLE
          queue: SOURCE.QUEUE.TEST
          username: USER_NAME
          password: changeit
      target:
        type: ARTEMIS_CORE
        config:
          broker-url: tcp://jms-broker.puzzle.ch:61617?sslEnabled=true
          username: USER_NAME
          password: changeit
          queue: TARGET_QUEUE_TEST
    - name: JMS_RESPONSE_TEST
      quality-of-service-mode: DUPLICATES_OK
      add-message-id-in-header: true
      source:
        type: ARTEMIS_CORE
        config:
          broker-url: tcp://jms-broker.puzzle.ch:61617?sslEnabled=true
          username: USER_NAME
          password: changeit
          queue: SOURCE_RESPONSE_QUEUE_TEST
      target:
        type: IBM_MQ_CLIENT
        config:
          host: 192.193.194.195
          port: 1414
          queue-manager: QMPUZZLE
          channel: COM.PUZZLE
          queue: TARGET.RESPONSE.QUEUE.TEST
          username: USER_NAME
          password: changeit

Das Root-Attribut jmsBridges definiert die Liste von JMS Bridges. Jede JMS Bridge kann eine Reihe von Attributen sowie eine Source- und Target-Konfiguration beinhalten. Source- und Target-Konfigurationen werden anhand des Attributes type spezifisch konfiguriert, d.h. können wiederum unterschiedliche Attribute enthalten – je nach Message Broker-Hersteller. In obigem Beispiel werden je ein IBM MQ Client und ein Artemis Core Client konfiguriert für eine «bidirektionale» Verbindung, d.h. jeweils eine ein- und ausgehende Verbindung. Ein Artemis AMQP Client lässt sich analog konfigurieren.

Fazit

  Ansatz / Option Vorteil Nachteil / Einschränkung Technischer Hinweis
  Interne Bridges in AMQ Artemis Keine zusätzlichen Komponenten nötig Nur Core-Protokoll unterstützt → nur Artemis↔Artemis
  Spring Boot Applikation Unterstützung verschiedener Protokolle & Broker durch standardisierte Interfaces Zusätzliche Komponente (Spring Boot App) nötig, die betrieben und gewartet werden muss Hohe Flexibilität durch einfache Erweiterbarkeit
  JMS Bridge im Application Server (z. B. JBoss EAP) Kein Core-Protokoll-Zwang, breitere Integration möglich Wartungsaufwändige und schwergewichtigere Komponente (EAP)
 

 

  • Die JMS Bridge von Apache basiert auf JMS Version 2.0 (Package javax.jms). Dies bedingt, dass neuere Spring Boot Applications, z.B. 3.2.x auf JMS 2.0 downgraded werden müssen, was bisher im Betrieb der Applikation zu keinem Nachteil geführt hat. Trotzdem können neuere Features von Spring Boot verwendet werden.
  • In diesem Artikel wird grundsätzlich mit den Begriffen «Destination» bzw. konkret «Queue» gearbeitet. Selbstverständlich kann anstelle von «Queue» auch ein «Topic» konfiguriert werden.
  • Beim Weiterleiten der JMS Message werden die Properties der Message übernommen. Artemis unterstützt dabei nur Property Keys, welche einem Java Identifier entsprechen, z.B. ist TRACE_ID oder traceId gültig, hingegen trace-id nicht (AMQ139012: The property name ‹trace-id› is not a valid java identifier.)! Diese Restriktion ist etwas unschön, weil sie erst zur Laufzeit auftreten wird. Falls dieses Problem auftritt, müsste bereits bei der Source Destination korrigiert werden (falls möglich) bzw. ein Pre-Message Processing forciert werden.

Quellen und weitere Informationen

Das Beispiel stammt aus dem Mandat bei der Bank Vontobel AG, wo wir die Möglichkeiten der JMS Bridge analysieren und für verschiedene Message Broker-Anbindungen entwickeln und konfigurieren konnten. Vielen Dank an die Bank Vontobel, welche diese Publikation ermöglicht und unterstützt hat!