Using JMS 2.0 APIs to access Azure Service Bus
Published Feb 11 2021 02:30 PM 8,128 Views
Microsoft

[Created on 12 August, 2020, and updated on 22 January, 2021]

Original post is on Medium.

https://logico-jp.medium.com/using-jms-2-0-apis-to-access-azure-service-bus-9f458ec2bf20

 

Japanese edition is here.
https://logico-jp.io/2020/08/12/using-jms-2-0-apis-to-access-azure-service-bus/

 

As you may know, support for JMS 2.0 APIs on Azure Service Bus Premium Tier is generally available. This feature allows us to interact with Azure Service Bus through JMS over AMQP 1.0.

 

Announcing general availability support for Java Message Service (JMS) 2.0 API on Azure Service Bus Premium

https://azure.microsoft.com/updates/announcing-general-availability-support-for-java-message-service...

 

As of 22 January, 2021, “preview” still exists in the document title, but it will be modified shortly.

 

Use Java Message Service 2.0 API with Azure Service Bus Premium
https://docs.microsoft.com/azure/service-bus-messaging/how-to-use-java-message-service-20

 

Note that the following document title is similar to the previous one, but this document does not cover JMS 2.0 APIs but JMS 1.1 APIs in Standard tier.

 

Use the Java Message Service (JMS) with Azure Service Bus and AMQP 1.0
https://docs.microsoft.com/azure/service-bus-messaging/service-bus-java-how-to-use-jms-api-amqp

 

In fact, we can use JMS 1.1 APIs to connect Azure Service Bus standard tier, but support for JMS 1.1 APIs is limited as you see the warning message in the document.

 

Warning
The below guide caters to limited support for Java Message Service (JMS) 1.1 API and exists for Azure Service Bus standard tier only.
Full support for the Java Message Service (JMS) 2.0 API is available only on the Azure Service Bus Premium tier in preview, which is highly recommended.

 

Which tier of Azure Service Bus supports JMS 2.0 APIs?

Premium tier only supports JMS 2.0 APIs. If using JMS 2.0 APIs to interact with Azure Service Bus Standard or Basic tier, the following exception is thrown with the message of “Full JMS 2.0 parity over AMQP is a feature supported only by a Premium Messaging namespace”.

image.png

Which JMS features are supported?

You can find supported features in the following URL. Note that distributed transaction is not supported.

 

What JMS features are supported?
https://docs.microsoft.com/azure/service-bus-messaging/how-to-use-java-message-service-20#what-jms-f...

Dependencies

As of 22 January, 2021, the latest version is 0.0.7. Please check the latest one in maven central repository. You can either use jar file to build your applications, or resolve dependencies through Maven or Gradle.

 

azure-servicebus-jms – ServiceBus ConnectionFactory for JMS users
https://search.maven.org/artifact/com.microsoft.azure/azure-servicebus-jms

<dependency>
  <groupId>com.microsoft.azure</groupId>
  <artifactId>azure-servicebus-jms</artifactId>
  <version>0.0.9</version>
</dependency>
 
 

Give it a try!

Following the document pointed earlier, you can do it easily. Let me show you examples using JMSContext.

First of all, connection factory should be instantiated in Azure Service Bus specific manner.

import com.microsoft.azure.servicebus.jms.ServiceBusJmsConnectionFactory;
import com.microsoft.azure.servicebus.jms.ServiceBusJmsConnectionFactorySettings;

...

ServiceBusJmsConnectionFactorySettings connectionFactorySettings = new ServiceBusJmsConnectionFactorySettings();
connectionFactorySettings.setConnectionIdleTimeoutMS(20000);
ConnectionFactory factory = new ServiceBusJmsConnectionFactory("CONNECTION_STRING", connectionFactorySettings);
Then, application(s) can be implemented in the usual manner of JMS 2.0 APIs. The following snippet is an example to send a text message to a queue.
try (JMSContext jmsContext = factory.createContext() ) {
    // Create the queue and topic
    Queue queue = jmsContext.createQueue("QUEUE_NAME");
    // Create the JMS message producer
    JMSProducer producer = jmsContext.createProducer();
    // Create textmessage
    TextMessage msg = jmsContext.createTextMessage(String.format("message sent at %s", (new Date()).toString()));
    // send the message to the queue
    producer.send(queue, msg);
}
catch (JMSRuntimeException e) {
    e.printStackTrace();
}
 

The next snippet is an example to receive text messages from a queue.

try (JMSContext jmsContext = factory.createContext() ) {
    // Create the queue and topic
    Queue queue = jmsContext.createQueue("QUEUE_NAME");
    // set Message Listener
    JMSConsumer consumer = jmsContext.createConsumer(queue);
    // Listener implements MessageListener.
    consumer.setMessageListener(new Listener());
    System.out.println("Receiver is ready, waiting for messages...");
    System.out.println("press Ctrl+c to shutdown...");
    while (true) {
        Thread.sleep(1000);
    }
} catch(InterruptedException e) {
    e.printStackTrace();
}
Here is a code for message listener used in the sample listed above. It is pretty simple since it is used for checking messages only.
 
public class Listener implements MessageListener {
    public void onMessage(Message m) {
        try {
            TextMessage msg = (TextMessage) m;
            // Show message
            System.out.printf("[Dequeued message at %s] %s\n", (new Date()).toString(), msg.getText());
        } catch (JMSRuntimeException e) {
            e.printStackTrace();
        }
    }
}

Subscription name scheme

In case of using topic, we have generally to subscribe a topic in order to wait for messages and creating subscription in advance is required. If not created yet, a subscription is automatically created when creating a JMSConsumer object and generated subscription is deleted when closing JMSConsumer object. As of January 10, 2021, subscription name is generated with the following scheme.

1. JMSContext::createConsumer

In case of createConsumer method, subscription name is created as follows.

 

<GUID>$$ND

 

$ is a delimiter, and “ND” would stand for “non durable”. As createConsumer method doesn’t have any way to specify subscription name, GUID is used as an alternative subscription name. Even if setting clientID to JMSContext object, the specified clientID is not used in the subscription name.

 

image.png

2. JMSContext::createDurableConsumer

In case of createDurableConsumer method, subscription name is created as follows.

 

<subscriptionName>$<clientID>$D

 

$ is a delimiter, and “D” would stand for “durable”. Both subscriptionName and clientID are mandatory and should be set to JMSContext object in the application. As you know, calling createDurableConsumer method without ClientID specified result in IllegalStateRuntimeException.

 

image.png

3. JMSContext::createSharedConsumer

In case of createSharedConsumer method, subscription name is created as follows.

 

<subscriptionName>$<clientID>$ND

 

$ is a delimiter, and “ND” would stand for “non durable”. And subscriptionName is mandatory, while clientID is optional.

 

image.png

 

So, two delimiters “$$” can be found in a subscription name when calling createSharedConsumer method without ClientID specified.

 

image.png

4. JMSContext::createSharedDurableConsumer

With clientID specified, subscription name is created as follows (subscriptionName is mandatory).

 

<subscriptionName>$<clientID>$D

 

$ is a delimiter, and “D” would stand for “durable”. This scheme is the same as the scheme of JMSContext::createDurableConsumer.

Without clientID specified, subscription name is created as follows (subscriptionName is mandatory).

 

<subscriptionName>

 

image.png

Here is the table to describe relationship between subscription name specified in codes and subscription name displayed in Azure Portal.

 
Method used
in creating a consumer
<A>
subscription name
specified in codes
<B>
Client ID
Subscription Name
in Azure Portal
createConsumer N/A Optional <GUID>$$ND
createDurableConsumer Mandatory Mandatory <A>$<B>$D
createSharedConsumer Mandatory Mandatory <A>$<B>$ND
createSharedDurableConsumer Mandatory Mandatory <A> or <A>$<B>$D

 

What benefits for you?

When migrating existing Java applications running on application servers to cloud, some customers tend to choose “lift to IaaS” strategy rather than “modernization”. Such customers tend to describe the reasons why they made such decisions.

 

“Our company has no mind to migrate their applications to PaaS since huge migration work are required.”

 

“We decided to keep our applications as it was and not to modernize them, so we chose only lift to IaaS rather than migration to PaaS in order to decrease OPEX.”

 

“We have to continue to use application servers to host our applications since the applications heavily rely on JMS and application server’s features. As of now, just simple migration to IaaS would be the best option for us.”

 

“Distribution transactions are mandatory…”

If a) a managed message broker is required, b) JMS 2.0 APIs are used in existing applications, and c) you have a plan to modernize your applications running on application servers, Azure Service Bus might fit you. Furthermore, if you are familiar with JMS 2.0 APIs, you don’t have to spend lots of time to learn how to develop applications with Azure Service Bus.

 

Conclusion

I confirmed Java application could interact with Azure Service Bus through JMS 2.0 APIs.

My sample codes are available on GitHub.

 

Access Azure Service Bus through JMS 2.0 APIs

https://github.com/anishi1222/Azure-Service-Bus-through-JMS-2.0-APIs

6 Comments
Copper Contributor

Hello, thanks for the explanation.

In my use case, i'd like to create non durable non shared topic subscriptions.
I managed to make this work with @jmslistener with a ServiceBusJmsConnectionFactory but it doesnt work if I use a JmsConnectionFactory.

I'm still able to send messages in the topic with the JmsConnectionFactory but im not able to subscribe.

If I use a JmsConnectionFactory, it fails to create the temporary subscription and it returns an error saying it did not found the messaging entity.

Do you know if it is expected behavior and im forced to use the ServiceBusConnectionFactory or i'm misconfiguring something ?

Thanks

 

Microsoft

My apologizes for late response.
We have to use not JMSConnectionFactory but ServiceBusJmsConnectionFactory when connecting Azure Service Bus via JMS 2.0 API. The reason why we have to do is that Azure Service Bus accepts using the connection string only, as you can see the following document.


Connection factory
https://docs.microsoft.com/en-us/azure/service-bus-messaging/jms-developer-guide?tabs=JMS-20#connect...

 

Microsoft

Hello Nishikawasan,

 

I am trying to update the Jakarta EE Cargo Tracker to use Service Bus as its JMS provider.  I observe that the call to 

 

factory = new ServiceBusJmsConnectionFactory(ServiceBusConnectionString, connFactorySettings);

 

simply never returns.  See this commit.  Line 45 or 48 is never reached.  (8 or 11 in the below snippet):

 

    System.out.println("debug: edburns: timeOut set.");
    String ServiceBusConnectionString = "REEDACTED";
    ConnectionFactory factory = null;
    try {
      System.out.println("debug: edburns: about to create ServiceBusJmsConnectionFactory");
      factory = new ServiceBusJmsConnectionFactory(ServiceBusConnectionString, connFactorySettings);
    } catch (Exception e) {
      System.out.println("debug: edburns: exception: " + e.getMessage());
    }

    System.out.println("debug: edburns: factory: " + factory);

 

An exception is not thrown, and the constructor just blocks.

 

What is a best practice for debugging this?

 

I do believe I have a valid connection string, because I do see some time-correlated activity in my namespace in the Azure portal.

 

edburns_0-1648208616310.png

 

Thanks,

 

Ed

 

 

 

 

 

Microsoft

@edburns san,

Thank you for reaching out and sorry for my late reply.

 

I have no idea to debug this issue happened in your environment. As "catch( Exception e )" is in your code, JMSException and JMSSecurityException should be caught here. Standard output is not properly for debugging, but it does not seem relevant.

It seems difficult to get useful information from logs even if log level is set to DEBUG since few logs are injected to codes in SDK repository (https://github.com/Azure/azure-servicebus-jms/tree/master/src/main/java/com/microsoft/azure/serviceb...).

# Indeed, if you created dependencies by yourself locally, you would get better information for debugging your code. However, I understand you don't want to do so!

 

If possible, could you please check if you can access your Service Bus instance with qpid-jms-1.6.0 API? Apache Qpid APIs are used in Service Bus JMS APIs, so if you could access the instance with only Apache Qpid APIs, you might find some hints.

 

Additionally, I ran cargo tracker app which contained modified JmsApplicationEvents.java and dependencies on Service Bus (com.microsoft.azure.azure-servicebus-jms:0.0.9) on Payara Server 5 (Payara Server 5.2022.1), and I confirmed the app worked fine.

 

 

  @PostConstruct
  private void postConstruct() {
    ServiceBusJmsConnectionFactorySettings connFactorySettings = new ServiceBusJmsConnectionFactorySettings();
    logger.log(Level.INFO, "debug: edburns: connFactorySettings: {0}", connFactorySettings);
    connFactorySettings.setConnectionIdleTimeoutMS(20000);
    logger.log(Level.INFO, "debug: edburns: timeOut set.");
    String ServiceBusConnectionString = "REDACTED";
    ConnectionFactory factory = null;
    try {
      logger.log(Level.INFO, "debug: edburns: about to create ServiceBusJmsConnectionFactory");
      factory = new ServiceBusJmsConnectionFactory(ServiceBusConnectionString, connFactorySettings);
    } catch (Exception e) {
      logger.log(Level.SEVERE, "debug: edburns: exception: {0}", e.getMessage());
    }

    logger.log(Level.INFO, "debug: edburns: factory: {0}", factory);

    jmsContext = factory.createContext();
    logger.log(Level.INFO, "debug: edburns: jmsContext: {0}", jmsContext);
  }

 

 

When I raised an event with Event Logger on Cargo Tracker app, I could see all logs injected to JmsApplicationEvents.java via Payara raw log viewer.

Logico_jp_0-1649222923667.png

Here is configuration list of my Service Bus instance.

  • Local authentication scheme is enabled.
  • My instance accepts connections from public IP of my house, and private endpoint.
  • For testing purpose, I use one of connection strings in RootManageSharedAccessKey.

Regards,

Aki

Copper Contributor

Hi @Logico_jp , we are in a Azure Service bus Premium subscription, with JMS 2.0 in our services, can we use Standard tier? Is it supported? If not, what alternatives we have to cutdown the cost/expense?

Microsoft

Hi @madanmr,

unfortunately, JMS 2.0 support is provided in Premium tier only and this condition still continues.

Use Java Message Service 2.0 API with Azure Service Bus Premium - Azure Service Bus | Microsoft Lear...

Depending upon your system requirements, AMQP, which is also available on Service Bus standard, is one of the alternatives to JMS. Azure Service Bus Standard also provides us limited support for JMS 1.1 Queue, but I don't recommend this configuration because this limited support might not work for your system.

Use AMQP with the Java Message Service 1.1 API and Azure Service Bus standard - Azure Service Bus | ...

Co-Authors
Version history
Last update:
‎Apr 05 2022 06:19 AM
Updated by: