First MicroProfile.io application using Java Module System

Embrace the JPMS power with MicroProfile.io applications

Update

Sample application migrated to use JDK 11.

MicroProfile.io

Eclipse MicroProfile project is growing fast, it has a lot of supporters and compatible implementations.

I see it as a new way to package enterprise Java Enterprise applications that already leverage of existing Java Specifications (JSRs) and want to be compatible with different platforms, specially ready for running in Cloud.

It also aims to reduce the gap between community feedback and release of new features.

I would like to mention that in addition of well know MicroProfile implementations like Payara Micro, WildFly Swarm, TomEE and Websphere Liberty, there's also Hammock, which I'll use in this post.

Hammock is an open-source project that allows the creation of CDI based microservices using Microprofile.io specification and additional starter modules such as JPA (Hibernate/EclipseLink/OpenJPA), Security (JWT, Keycloak), Messaging (Artemis/RabbitMQ) or your own starter module, just like Spring Boot does, but in a CDI way.

Currently in development version 2.2 supports most of MicroProfile.io 1.3 capabilities.

First MicroProfile.io JPMS application

There're few ways to start using Hammock, the easiest one is to add the following maven dependency.

<dependency>
    <groupId>ws.ament.hammock</groupId>
    <artifactId>dist-microprofile</artifactId>
    <version>${hammock.version}</version>
</dependency>

Personally, as I want to ensure my application dependencies are thin I prefer to use the DIY way, adding each module individually (CDI, REST, JSON), so I can choose which implementation of CDI, REST and JSON I’ll use.

It also helps us to fix JPMS comparability issues that I found during the implementation of (my) first MicroProfile.io JPMS application (I have not heard of any other on the internet).

As usual, I already explained in previous post Upgrade to Java 10 now! Why not? in which I only add module-info.java and deal with application dependencies to start using module system.

The problem

Java Module System (JPMS) is still recent and thirdparty libraries and frameworks are trying to catch up to support modularization, so we will need a few tweaks, like preventing two JARs with the same package names conflicts in the classpath (that was already a hidden problem, because you know, API changes and first class on classpath wins).

With JPMS, it prevents two modules (or automatic modules) to export the same package.

Solution

The sample application is a basic Hello World JAX-RS resource using JSON-B.

package hammock.jpms;

import javax.enterprise.context.RequestScoped;
import javax.ws.rs.*;

@Path("/hello")
@RequestScoped
public class HelloResource {

    @GET
    public Person hello() {
        Person person = new Person();
        person.setName("Leonardo");
        return person;
    }
}

With the following module descriptor:

open module hammock.jpms {

    requires cdi.api;
    requires hammock.core;
    requires java.json.bind;
    requires java.ws.rs;
    requires java.xml;
    requires jdk.unsupported;
    requires org.apache.logging.log4j;

}

View the Maven project descriptor (pom.xml) to see the dependencies and plugins declarations.

Results

The total size of this example application is around 12mb and the startup time of the whole application is around 1s in my machine.

Also the maximum memory usage (RSS) of the JVM was 133mb in my tests.

Note: The maximum heap size used in tests was -Xmx128m.

Put Things Together using JLink and Docker

Following the same approach of previous post Create a Cloud Native Image using Java Modules I can create a minimal JVM to run the application and generate a Docker image.

FROM panga/openjdk-alpine:11-jdk as builder

RUN jlink \
    --add-modules java.logging,java.xml,java.naming,java.management,jdk.unsupported \
    --verbose \
    --strip-debug \
    --compress 2 \
    --no-header-files \
    --no-man-pages \
    --output /opt/jre-minimal

FROM alpine:3.8

COPY --from=builder /opt/jre-minimal /opt/jre-minimal

ENV LANG=C.UTF-8 \
    PATH=${PATH}:/opt/jre-minimal/bin

ADD modules /opt/app/modules
ADD patch/java.beans /opt/app/patch/java.beans

ARG JVM_OPTS
ENV JVM_OPTS=${JVM_OPTS}

CMD java ${JVM_OPTS} \
    --patch-module java.base=/opt/app/patch/java.beans \
    --add-exports java.base/java.beans=johnzon.mapper \
    --add-exports java.base/java.beans=org.apache.logging.log4j.core \
    --module-path /opt/app/modules \
    --module hammock.jpms

The resulting was an amazing 54.3mb Docker image with only minimal JRE and application modules.

If you look carefully, you will notice that a patch-module was added in the command line in order to hack JPMS and remove java.desktop module dependency. We really don't need Swing/AWT classes here!

I was only able to achieve that using my new pet project LiteBeans, a java.beans package patch and alternative API implementation used to remove java.desktop module requirement on JDK 9 and up.

panga/lite-beans
lite-beans - LiteBeans is implementation of the java.beans package based on the Apache Harmony projectgithub.com

Conclusion

The CDI is the new glue and Docker the new hammer.

Finally, you can find the source in this repository: panga/hammock-jpms

Also the instructions to build and run are available in the README.

panga/hammock-jpms
hammock-jpms - Hammock MicroProfile (CDI + JAXRS + JSON) HelloWorld using Java Module System (JPMS)github.com