Migration from Java EE to JakartaEE and Microprofile

Some years ago I wrote an article on LinkedIn about how to avoid mapping DTOs unnecessarily and use the JSON-P API from JavaEE to do the job for you.

Looking back at the article I feel that it serves it’s purpose, but in terms of code I missed some things that were happening by the time, for instance, the new set of specifications that was created in a time of uncertainty on the paths that Oracle was taking with Java EE, this set of specifications is known as Microprofile, and by the time of that article, if I’m not mistaken, its version was 1.3, and today (if you are reading this in December 2021 =D) it’s already in version 5.0.

Also, another important landmark was the migration of JavaEE to a new house, with a new name (Jakarta EE), a new release cycle, and a huge community moving the project forward. If you want to know more about it you can visit the project website and the YouTube Channel where you can see all the previous livestreams of JakartaOne with coding session, interviews and information related to the project.

So, I think it’s justified a new article explaining how to migrate my old project to the newest tooling available at the moment in the Jakarta EE world (consider Microprofile included).

Where we left

The tools you will need are:

  • Java (I’ll be using 17, but you can adapt and use the one that suits you best), you can download it easily using SDKMan;
  • Maven (You can also use Gradle, again, it’s up to you to use and adapt what is needed)
  • An IDE of choice;

Jakarta EE

So, let’s do this.

Disclaimer

Another change I had to do in order to use JDK 17 smoothly was to migrate from Payara to Open-liberty since it already supports this version. Quarkus and Helidon also supports JDK 17, but this is a subject for maybe another article.

pom.xml

Inside our pom.xml, the first thing we will change to upgrade from JDK 11 to 17 and from javaee to jakartaee as follows:

You project now should be full of errors that it cannot find any package name javax, the reason for this, as explained before, is that all the APIs now lives under the jakartaee namespace.

Migrating the namespace

For my code my changes I used the third option. But all the other projects have the proper documentation to help you with the task.

CDI, what changed

Since we migrated the namespace of everything to JakartaEE, we should do the same for the beans.xml file. It should now be as follow:

Running the project

With all the errors fixed, it’s time to run the project using OpenLiberty.

We will be using the maven plugin to make our life easier here, we just need another change so the application server knows which feature we are using (Open Liberty needs you to tell what you will be using with it so it only loads the necessary for you app to run).

To do this we need to create a new folder under src/main called liberty and inside it a folder called config with a file name server.xml:

We are telling the server to run with full support for JakartaEE 9.1.

After that, in a terminal window, run mvn liberty:run and Open Liberty plugin will take care of all dependencies you need in order to run you project.

You should see a message like this in the terminal:

The defaultServer server is ready to run a smarter planet. The defaultServer server started in 5,364 seconds.

This means you are good to go. Type (or copy) this URL in your browser http://localhost:9080/beverage-suggester/resources/beverages and you should see a random beverage in response.

There you go, now you have migrated your application to the newest JakartaEE version.

The biggest change of this version was migrate all APIs to the new namespace, I believe this is the first time the project ever broke compatibility with past releases, but this was a necessary step in order to evolve the APIs and the project further. You can see what is planned for the next version here.

Microprofile

To get started we need to update our pom.xml, simply add the following dependency:

And also change the server.xml config of Open Liberty to let it know that we will make use of the Microprofile module, to do this add this feature to the file:

Now, whenever you start the server, you should see the following:

The server installed the following features: 
[appAuthentication-2.0, appAuthorization-2.0, appClientSupport-2.0, appSecurity-4.0, batch-2.0, beanValidation-3.0, cdi-3.0, concurrent-2.0, connectors-2.0, connectorsInboundSecurity-2.0, distributedMap-1.0, enterpriseBeans-4.0, enterpriseBeansHome-4.0, enterpriseBeansLite-4.0, enterpriseBeansPersistentTimer-4.0, enterpriseBeansRemote-4.0, expressionLanguage-4.0, faces-3.0, jakartaee-9.1, jdbc-4.2, jndi-1.0, json-1.0, jsonb-2.0, jsonp-2.0, jwt-1.0, mail-2.0, managedBeans-2.0, mdb-4.0, messaging-3.0, messagingClient-3.0, messagingSecurity-3.0, messagingServer-3.0, microProfile-5.0, monitor-1.0, mpConfig-3.0, mpFaultTolerance-4.0, mpHealth-4.0, mpJwt-2.0, mpMetrics-4.0, mpOpenAPI-3.0, mpOpenTracing-3.0, mpRestClient-3.0, pages-3.0, persistence-3.0, persistenceContainer-3.0, restConnector-2.0, restfulWS-3.0, restfulWSClient-3.0, servlet-5.0, ssl-1.0, transportSecurity-1.0, webProfile-9.1, websocket-2.0, xmlBinding-3.0, xmlWS-3.0].

This shows us that the JakartaEE and Microprofile module is enabled on the server with its correspondent APIs and versions.

MP-Rest Client

Microprofile Rest Client is a type-safe way of creating http rest clients through the use of interfaces. If you are familiar with Feign, the idea is exactly the same, but it is seamlessly integrated with JAX-RS APIs.

In the previous version of the application, we manually created the client to communicate with the open weather map api:

To refactor this code to make use of microprofile, we need to create an interface, add the @RegisterRestClient and map the HTTP method that we will be using with the well known JAX-RS annotations, here is the full snippet:

The new TemperatureService class now should look like this:

Less things to reason about, right?

Note that to inject our client we need to annotate it with @RestClient.

Improving the code organization

Moving away a bit from just the migration topic, let’s use another feature of the Rest Client to add some quality in our code. You may be wondering what can be wrong with the TemperatureService now since we removed already some boilerplate code from it, others may also have noticed that our service class is dealing with the transformation of the JsonObject from our external api into our desired type.

And this is what we are tackling now, we will add a bit of Single Responsibility to our code.

To do that we will need to move the transformation logic alway from the service, ideally we would make the client call return a Double for us so the service does not even need to know what happens behind the scenes, it just need to worry about receiving a double value.

So we will make use of the ReaderInterceptor interface to be able to make this transformation for us.

Lets create our TemperatureReaderInterceptor:

As you can see, it’s a pretty simple interface with only one method that is called after we already have a response from the external API, but before returning to the original caller. We also made changed the way we read the response, now we get the InputStream from the context and create a reader from it inside the try-with-resources construct and then we read the JsonObject from it using the readObject method. The rest of the code is exactly the same as before (apart from the final var usage)

In order to use the interceptor we need to register it in our client interface, to do this we simply need to add the annotation @RegisterProviderto our interface and change the return type to Double.

Now we refactor a bit more our TemperatureServiceclass:

One other thing we should think of is how to react in case of errors when calling the external service, I will not be designing our service to deal with possible failures in this article, but for now I will show you how you can deal with exception in an isolated way so you can translate the external client errors in an internal error that you and your team will understand better.

To do this we need to implement another interface provided by Microprofile RestClient called ResponseExceptionMapper and register it as a provider the same way we did with the interceptor created before.

Here is how I created this:

First thing to notice is that I’m only interested in response with status codes from the family ClientError or ServerError.

To keep it simple and demonstrate the purpose I’m only translating the response into an internal exception with a message string for 401 and 403 or in other cases simply translate to an exception with the response object. (notice the use of the new switch expression)

Don’t forget to register the mapper into the client:

One last thing to say about the RestClient APIs is that it’s fully integrated with the next Microprofile API that I will show, Microprofile Config, you can check the documentation to see what is possible to do in terms of external configuration of you rest client.

MP-Config

An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc). This includes:

* Resource handles to the database, Memcached, and other backing services

* Credentials to external services such as Amazon S3 or Twitter

* Per-deploy values such as the canonical hostname for the deploy

Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.

Microprofile Config came to close this gap in the Jakarta EE world and if you want to know more about the project check the github page to have a deeper look in the decisions behind it.

In our project, there some things we can definitely externalize from the code:

  • The TemperatureClient URI;
  • The default unit that we use to call the external service;
  • The appId to call the external service;

And I will be doing this now.

First we need a file to hold this information, and in microprofile config this file should be under META-INF folder named microprofile-config.properties.

The first thing I’ll do is remove the URI from our client and add to our properties file:

br.com.beveragesuggester.control.TemperatureClient/mp-rest/url=https://api.openweathermap.org/data/2.5

Notice that we need the full name of the class followed by the microprofile specific config for the rest client, this information can be found in the documentation of the rest client project.

Next, I’ll create two more entries in the properties file, one for the unit and one for the appId property:

temperature.apiKey=<APP_ID>
temperature.unit=metric

To keep the configuration together in the code and also facilitate the maintenance and test in the future, I created a class to hold all configuration that starts with the prefix temperature:

You can see that I make use of the @ConfigProperties annotation to inform the prefix that this class should be mapped to, and I also included a default value for the unit property in case none is passed.

To use the configuration we just created we can inject it into our TemperatureService class:

Now that we are making use of the Config API we could overwrite the properties with system properties, environment variables depending on the env, making our code configurable without the need to recompile everything.

You can find all the code presented in this article on github.

Please, leave comments so I can know where to improve for future articles.

See you next time!

Developer working in the e-mobility industry.