Java EE, Jakarta EE, MicroProfile, or Maybe All of Them
It seems that more and more enterprise technology is emerging that is based on Java EE. There are a lot of options to choose from, between Java EE (now referred to as Jakarta EE), MicroProfile, and combinations of their APIs. If we look at available application containers, the number of possibilities is even higher. Which platforms, particular standards, and runtimes should enterprise developers base their applications on in year 2019?
Java EE Today
Let's start with Java EE. There is a magnitude of popular Java EE standards that are widely used, and that developers know well and, based on personal experience, enjoy using. Examples for such standards are CDI, JAX-RS, JSON-B, or JPA. You can use these and other standards to efficiently develop enterprise applications.
So why isn't it enough to just use Java EE, then? In the age of microservices, our applications have to cover non-functional requirements that are important once we want to run our applications in production. Requirements such as resiliency, monitoring, or distributed tracing are critical. These are currently not covered by standard Java EE. Another, often criticized aspect is that plain Java EE doesn't easily support injectable configuration.
Now, we could argue that these aspects aren't actually that new but have been required in the last decades. That is totally true; however, especially in the age of microservices in which we tend to distribute systems more and more, these requirements become more critical. Now, the industry seems to care. In particular, cloud-native applications are required to implement resiliency and observability.
If plain Java EE isn't quite enough, what's the deal with Jakarta EE? Jakarta EE is the successor of Java EE, which has been transferred to the Eclipse Foundation. Currently, the process under which the future, vendor-independent standards will be developed is being formed. Thus, we're still in the ramp-up phase, and there is no exclusive version of Jakarta EE that developers could grab and use, yet. For this reason, I'll continue to use Java EE for the rest of this article, when we're talking about the standards that are part of Java EE 8 today.
MicroProfile is another initiative within the Enterprise Java ecosystem. MicroProfile was initiated by multiple vendors to, on the one hand, be able to advance the development of vendor-independent enterprise technology, in a time in which one could not see much progress from Oracle's side. On the other hand, the ecosystem wanted a way of realizing microservice deployments in which the implementations only ship what's actually required by the individual applications.
MicroProfile builds upon Java EE standards and their design principles. For example, MicroProfile 1.0 only consisted of CDI, JAX-RS, and JSON-P. The individual MicroProfile projects extend the standards with functionality that isn't part of Java EE today (for example, configuration, resiliency, monitoring, or distributed tracing).
The speed with which new MicroProfile projects emerge and advance is pretty impressive. In 2018 alone, we saw the advent of MicroProfile 1.3, 1.4, 2.0, and 2.1, and the projects contained in these.
So, if MicroProfile does now ship with all these projects, why don't we simply bet on MicroProfile as our sole technology of choice - especially since there are a handful of runtimes that support deploying plain MicroProfile applications?
MicroProfile alone is mostly insufficient to develop more complex enterprise applications (for example, out-of-the-box support for persistence, as in JPA or JTA). The same is true for more complex concurrency, which would be solved with the Concurrency API in Java EE.
For this reason, we still have to build upon Java EE for certain aspects. The following examples will demonstrate why this makes sense.
Java EE and MicroProfile: The best of both worlds
Since MicroProfile is based on Java EE standards, we can integrate MicroProfile projects in plain Java EE applications. If we're using an application container that supports both Java EE and MicroProfile, we can even do that without shipping additional implementations in our deployment artifacts.
Let's look at an example.
Our coffee example application consists of two Java EE microservices - coffee shop and barista - that are supposed to run in Docker containers in a cloud-native environment.
We want to enhance the coffee shop service with resiliency and injectable configuration. In order to do so, we add the dependencies for MicroProfile Fault Tolerance and MicroProfile Config to our Maven pom.xml.
We declare the dependencies as provided, to ensure that our deployment artifact stays lean. Our application container will already know about these APIs.
Configuring cloud-native applications
Let's start with configuration. The opinionated way to configure cloud-native applications is from "outside" the application. That is, we don't re-configure or change the application's binary (for example, the Docker image), but we apply the desired injection at runtime. This typically happens using UNIX environment variables or Docker volume files. If we're using Kubernetes as container orchestration, it will be responsible to inject these artifacts. The 12 factors of modern enterprise applications describe the motivation behind this method of configuration.
Our application should be able to use the configured values with minimal developer effort involved. MicroProfile Config ships with various default configuration sources, such as environment variables.
The following JAX-RS health check resource shows how to inject a configured value:
Our application server's version is contained in the environment variable VERSION, which is automatically read by MicroProfile Config. Case sensitivity is irrelevant here.
When accessed with /health, the health check resource responds with an OK status including the version as HTTP header. That's all we developers have to do. As long as our application supports MicroProfile Config, the default configuration sources are available.
Our coffee shop application communicates with a second service, barista, using HTTP. If the back end becomes unavailable, our application shouldn't be disturbed too much by that. For this reason, it's crucial to define reasonable timeouts. What's more, the circuit breaker patterns support us by not making too many pointless connections to an unresponsive back end in production.
The following snippet shows the Barista class, which communicates with the barista backend:
The timeout configuration methods are included in the JAX-RS client since JAX-RS version 2.1 and define the timeout behavior of the underlying HTTP client.
The @CircuitBreaker annotation enhances the startCoffeeBrew method with circuit breaker behavior. If more of the invocations fail (by default, more than 50%), an exception is thrown and, within a window of 20 invocations and 5 seconds, the circuit will be opened. This means that subsequent invocations will immediately fail, without even executing the actual method. After another 5 seconds, the method will be attempted to execute again, and depending on whether we receive another exception, the circuit stays open or will be closed again. We can change these default values in the @CircuitBreaker annotation, as well as the exception type that trips the circuit breaker, and a potential fallback behavior. For more information, refer to the MicroProfile Fault Tolerance documentation.
These small examples aim to demonstrate how MicroProfile facilitates to enhance our Java Enterprise applications with required functionality, such as configuration or resiliency. Of course we could implement functionality like this circuit breaker with plain Java EE, but that would mean that we need to implement the code ourselves, in each and every of our microservice applications.
To make these examples work, we need to deploy our applications to application containers that support both Java EE and MicroProfile. Ideally, we can declare all dependencies as provided to make use of thin deployment artifacts.
The example applications are deployed to Open Liberty, which supports both Java EE 8 and MicroProfile 2.1. The required features are specified in the server.xml configuration:
By doing so, the application server will support the individual MicroProfile projects that are being used in the applications. If we don't want to specify individual features, we can also make use of the umbrella microProfile-2.1 feature. Furthermore, we could even break up the javaee-8.0 feature and replace it with only the standards that we actually use in our projects.
Payara, Tom EE, and WildFly are other application servers that support both Java EE and the current versions of MicroProfile, and thus enable this programming model as well.
Into the Future: Using MicroProfile as an Incubator for Jakarta EE
Currently, we're still in the middle of the ramp-up phase of Jakarta EE and are looking towards potential future directions of the MicroProfile strategy. One possible, and arguably reasonable, direction is to look at MicroProfile as an incubator of Jakarta EE standards. By doing so, the enterprise ecosystem can advance to develop vendor-independent technology faster, without immediately forming everything into official standards, which need to be supported for a long time. Since various vendors support MicroProfile already, these incubator projects would be vendor-independent and much closer to standard technologies, unlike what we had in the past. Functionality that proves itself would then be integrated into Jakarta EE. This would not only enable a long-term evolution of Jakarta EE, but also faster innovation.
It makes a lot of sense for enterprise projects to use APIs that are known to developers. The Java EE APIs are widely used, yet they have some gaps that can be filled by MicroProfile projects. Thus, we can think of MicroProfile as an extension of the existing Java EE standards. Software developers can continue to develop their applications using the known APIs and still avoid implementing boilerplate patterns, such as injectable configuration or circuit breakers themselves.
Similarly, it makes sense to choose a deployment model that fits the application and the cloud-native environment. Using thin deployment artifacts makes the development process more productive. To enable this, we can deploy our applications to containers that support both Java EE and MicroProfile. My advice is to optimize the deployment artifacts before starting to optimize the runtime environments. For example, put dependencies into the containers before starting to trim down the server installation. The artifacts will be built and transmitted with each and every build execution, and should not include runtime dependencies.
*This article was originally published on the IBM Developer website.*