Supporting MQ Jakarta JMS in Spring Boot

One of the features of the newly-released MQ 9.3 is support for JMS 3, also known as Jakarta Messaging. There will be more information elsewhere about what that means for standalone JMS programs. There continues to be a JMS 2 package, of course, for ongoing compatibility. But JMS 3 introduces incompatibilities that mean that we need updated versions of other components to match if you want to move up to newer standards. This post will talk about supporting MQ Jakarta JMS in Spring Boot.

I’ll also discuss the issues I had when developing the new version – the annoying incompatible tooling upgrades I had to work through.

What is Jakarta Messaging

Very simply, Jakarta is the latest iteration of the JEE (Java Enterprise Edition) standards. Maintenance and enhancement of the standards have moved to a new organisation. For legal/licensing reasons, the Java packages associated with these standards have mostly had to change name. In turn, that means there is a migration required in application source code. Essentially you have to change almost all of your Java code so that import javax.xxxx becomes import jakarta.xxxx. As part of this move, the component technologies are being renumbered. So now we have JMS 3.0. There is no functional difference between JMS 2.0 and JMS 3.0 to begin with (perhaps the standard will evolve in future); the only real change so far is in the package names.

The source code incompatibilities mean that most providers of JEE technologies including IBM MQ will – for some period – have two implementations. We expect that over time people will migrate their apps to using the Jakarta namespace. The modications can be mostly automated, but it’s still not going to be a quick changeover.

There are tools that claim to edit binary jar files so you do not need to edit and recompile. These are not tools that MQ has done any formal testing with so we can’t say how successful you might be when using them.

The MQ JMS classes

MQ follows the dual implementation pattern. It now provides two versions of the client jar file. They have the same version number (9.3.0.0 to start with), but the artifact name tells you which is which – com.ibm.mq.allclient and com.ibm.mq.jakarta.client. Similar variations exist for the MQ Resource Adapter when it run inside an application server. Liberty, for example, has two editions – one based on Jakarta and the other based on the older JEE packages. You then pick the appropriate MQ RA for the environment.

Using MQ in Spring Boot

I’ve written previously about using MQ in Spring Boot. So I’m not going to repeat that here. But just like the application servers, there is a new version of Spring under development that switches to the Jakarta model. And so we need a version of the MQ Spring Boot Starter to match.

Spring Boot itself is not yet at an official supported level for using the Jakarta interfaces, but you can work with Milestone/Snapshot levels from their repositories. The Spring project tells us to expect a formal release level towards the end of 2022.

I have now released MQ Spring Boot Starter 0.3.0-M3 to match Spring Boot 3.0.0-M3. It is the same function as the 2.7.1 version released at the same time, using the JMS 2 components. I normally try to keep the MQ module at the same version number as the corresponding Spring Boot level it is built against. For now, however, I have deliberately chosen a temporary-looking number. That way, a new release will show up at the top of the list after the official Spring Boot 3 is available.

The github repository has also been updated with a new sample that uses the Jakarta dependencies. You can see a pre-release version of Spring Boot in use in this screenshot:

Spring Boot 3 sample program
Running the Jakarta sample

If you compare the code in samples/s3.jms3 with the code in samples/s3, the only changes are to the import statements:

$ diff s3.jms3/src/main/java/sample3/Responder.java s3/src/main/java/sample3
 17,21c17,21
< import jakarta.jms.JMSException;
< import jakarta.jms.Message;
< import jakarta.jms.MessageProducer;
< import jakarta.jms.Session;
< import jakarta.jms.TextMessage;
———
> import javax.jms.JMSException;
> import javax.jms.Message;
> import javax.jms.MessageProducer;
> import javax.jms.Session;
> import javax.jms.TextMessage;

So if you want to experiment with the Jakarta-based Spring Boot, you can now use it with MQ as the JMS provider. The module has the same group and artifactId for both implementations. Just the version is different:

<dependency>
  <groupId>com.ibm.mq</groupId>
  <artifactId>mq-jms-spring-boot-starter</artifactId>
  <version>0.3.0-M3</version>
</dependency>

or

<dependency>
  <groupId>com.ibm.mq</groupId>
  <artifactId>mq-jms-spring-boot-starter</artifactId>
  <version>2.7.1</version>
</dependency>

Updating the MQ Spring Boot code

Making the changes to get the new MQ Spring Boot Starter jar file should have been very easy. No real code changes ought to be needed, just rewriting the import statements. To keep things simple, and to stick with a single set of source code, I decided to use a script at build time that clones the source tree into a temporary directory. I would just change javax to jakarta during the copying. The build.gradle control file then pulls in appropriate definitions and dependencies from version-specific property files to create both forms of the jar.

This is essentially how the MQ client jar itself is being managed for now in the product build process. Maybe there will have to be parallel development in two source trees if there is ever a JMS 3.1 standard with new function. But for now, there should be no need to have two separate copies of the source.

The RUNME.sh script recompiles the starter jar from its source code. It sets up various control parameters and cleans out previous build attempts before driving gradle as the actual builder. The script can generate output for both JMS2 and JMS3 variations. It also makes various checks so you can verify correct building of the artifacts.

TOOLING Issues

I hit a number of issues doing what should have been a simple migration. They’re not going to be of interest to people who simply want to use the jar file, but I’m going to list them here to help anyone else who has to do similar updates. It was while I was working through all these annoying “upgrade incompatibilities” that I got the idea of demonstrating how well MQ has managed across many years of updates.

Few of these problems are actually to do with Jakarta – most of the problems were caused by enforced tool upgrades.

  • The new Spring Boot requires Java 17 as its minimum level. There is a documented and reasonable rationale for this large bump, but it meant I had to manage several levels of Java on my build machine as I still need a real Java 8 available. I can’t just set a 1.8 output compatibility flag for the Java 17 compiler in all circumstances. Although I can do that for this Spring Boot project when building the JMS 2 version of the starter, it doesn’t work for at least one other critical project I work on.
  • Upgrading to Java 17 broke gradle. A core component was doing something “bad” that the Java 17 runtime refused to tolerate. I had to upgrade to a recent level of gradle.
  • That newer level of gradle then required changes to the used plugins. It wasn’t just a version change. Publication of artifacts to Maven Central was one aspect that changed significantly.
  • The new plugins required substantial rewrites of the build.gradle file itself to use different tasks. I think the final version of this file is now much cleaner and easier to work with than how it started out. But it took a while to work through how best to manage tasks like the publication process. It also evolved as I worked out what aspects needed to be split out into JMS2 or JMS3-specific files or paths in the build process, and which could remain common.
  • One particular problem that took a while to solve was how to name and use dependencies in gradle. The original build file had lines like:
compile group:org.spring.framework, name:spring-core, version:5.3.19

But the upgraded java plugin for gradle decided to stop supporting the compile task.

All the high-ranked recommended solutions I could find online said to simply replace that with the implementation task. That appeared to work for building the jar. But sample programs using the jar could not then find various classes when compiled. It was only by looking at the generated pom.xml file (in particular the scope attribute for the dependencies) that lead me to the real answer. Instead of the recommended replacement, use api as the task. So the corresponding lines are now

api group:org.spring.framework, name:spring-core, version:5.3.19
  • I did a lot of work in the build process to make sure I was replicating locally as far as possible what happens when you use the real Maven Central repository. There’s a much better “publish to local directory tree” process than I had before. It includes checks that any listed dependencies do exist in the public repositories. While I was doing this work, the real MQ JMS3 client.jar file was not publically available. I had to replicate that publication and discovery process too.

Jakarta-caused issues

I experienced a number of other issues doing this work. Although the Jakarta renaming didn’t cause them directly, there were a couple of things to note:

  • The JmsPoolConnectionFactory interface did not have a Jakarta-compliant version for most of the time I worked on this project. Spring Boot removed references to it. So I wrote a temporary non-functional skeleton for copying in during the script that changes import package names, to keep the source code common.

But an updated version of the pooling component conveniently came out just in time for the MQ release. And Spring Boot reinstated it in their most recent Milestone release. So I have been able to remove that scaffolding.

  • Not all of the javax interfaces have been renamed to jakarta. Which meant that the conversion script has to be a little bit more sensitive than a simple global change. In particular, javax.naming remains untouched. My makeJms3.sh script is not a general-purpose script. It only changes enough to rebuild this component. But the concept might be reusable if you have other components to convert to the Jakarta packages.

Future MQ Spring Boot Starter versions

The Spring blog post announcing the Boot 3 project also said that at some (as yet unannounced) point enhancements to the Spring Boot framework will come only via the Jakarta path. The 2.x version will move to some kind of maintenance mode.

I expect that for now, we will continue to make parallel updates to both levels of the MQ Spring Boot starter. It will need to keep current with new versions of MQ even when the underlying Spring Boot 2.x itself is not changing. I’m sure there will be a time where we release new versions of this jar only for Spring Boot 3 but it’s too early to say right now when that might be. One thing that would drive that decision is if new features in JMS 3.x come out that make it harder to have a single source tree.

This post was last updated on June 30th, 2022 at 07:53 am

One thought on “Supporting MQ Jakarta JMS in Spring Boot”

Leave a Reply

Your email address will not be published. Required fields are marked *