Migrate Java microservices from Java 8 to Java 11

Introduction

Some people might have struggled to efficiently migrate their Java microservices from 8 to 11+.
There are 2 main reasons you’d want to do that:

  • Java 8 end of line coming soon ;
  • Java 9+ modules that help build “slimmer” JVM.

Use Case

Now, if you simply switch from Java 8 to Java 11 you’ll end up with microservices that will be 50% bigger in size and resource consumption, if you use the Maven plugin to build your microservice, that is. Not really slim…
Why is that? Actually the Maven plugin uses a default Dockerfile that will install the full JDK, whatever the version of Java you’re using. Meaning if you use Java 9+, you’ll end up with a docker image that contains all the java modules, even those that you don’t need…
You’re going to need a custom Dockerfile to take full advantage of the modules system of Java 11 that’ll use jlink to build a custom JVM that will include only the modules you need.
Here’s a sample dockerfile, that should fit for almost everyone (just change the project name in the COPY command):

FROM alpine:3 as base
RUN apk add  --no-cache openjdk@package.java-version@
RUN jlink \
--module-path /opt/java/jmods \
--compress=2 \
--add-modules java.se,jdk.unsupported,jdk.crypto.ec \
--no-header-files \
--no-man-pages \
--output /opt/jdk-mini
FROM alpine:3
COPY --from=base /opt/jdk-mini /opt/jdk-mini
RUN apk add  --no-cache coreutils
ENV JAVA_HOME=/opt/jdk-mini
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY etc/ /etc/@package.directory@/
ADD resources/* /data/
RUN chmod +x /data/entrypoint.sh
ENTRYPOINT /data/entrypoint.sh

Next question is: where to put this Dockerfile so that the Maven plugin will use it instead of the default one?
Pretty easy: just put it in src/main/docker/Dockerfile.
That’s it!
Regarding the dependencies: why java.se and jdk.unsupported?
That’s because of Spring (and Spring Boot) which is a hard dependency of Cumulocity Java SDK.
Spring depends on java.beans, which requires java.desktop, which itself includes… java.awt. And many other dependencies are also required. In the end including java.se is simpler and doesn’t cost much (maybe 1 or 2 MB compared to a better filtered JVM).
jdk.unsupported is also required by Spring.
jdk.crypto.ec is only necessary if your microservice needs to connect to a URL using TLS.

What about the default entrypoint.sh?
Good question indeed, as the default one is optimized for Java 8.
You might therefore want to use a custom one.
Here’s a simple one that take care of proxy configuration, which is especially important if you have an on-premises or edge installation of Cumulocity and your microservice require access to external resources:

proxy_params=""
if [ -n "$PROXY_HTTP_HOST" ]; then proxy_params="-Dhttp.proxyHost=${PROXY_HTTP_HOST} -DproxyHost=${PROXY_HTTP_HOST}"; fi
if [ -n "$PROXY_HTTP_PORT" ]; then proxy_params="${proxy_params} -Dhttp.proxyPort=${PROXY_HTTP_PORT} -DproxyPort=${PROXY_HTTP_PORT}"; fi
if [ -n "$PROXY_HTTP_NON_PROXY_HOSTS" ]; then proxy_params="${proxy_params} -Dhttp.nonProxyHosts=\"${PROXY_HTTP_NON_PROXY_HOSTS}\""; fi
if [ -n "$PROXY_HTTPS_HOST" ]; then proxy_params="${proxy_params} -Dhttps.proxyHost=${PROXY_HTTPS_HOST}"; fi
if [ -n "$PROXY_HTTPS_PORT" ]; then proxy_params="${proxy_params} -Dhttps.proxyPort=${PROXY_HTTPS_PORT}"; fi
if [ -n "$PROXY_SOCKS_HOST" ]; then proxy_params="${proxy_params} -DsocksProxyHost=${PROXY_SOCKS_HOST}"; fi
if [ -n "$PROXY_SOCKS_PORT" ]; then proxy_params="${proxy_params} -DsocksProxyPort=${PROXY_SOCKS_PORT}"; fi

echo "Proxy settings found: " $proxy_params

mkdir -p /var/log/@package.name@; echo "heap dumps  /var/log/@package.name@/heap-dump-<pid>.hprof"

java -XX:MinRAMPercentage=60.0 -XX:MaxRAMPercentage=90.0 -XX:+HeapDumpOnOutOfMemoryError -XshowSettings:vm ${proxy_params} -jar /data/@package.name@.jar

You’ll need to adapt the JVM parameters according to your needs and what your microservice is doing (for example setting MinRAMPercentage high enough might help start your microservice quicker if it needs to create a lot of objects on startup).
Finally, you will want to put this file in src/main/docker/resources/entrypoint.sh.

Hope that will help!

Useful resources

If you want more info regarding java modules and how to use them efficiently, here’s a great Devoxx UK session on Youtube (this helped me figure out that I needed jdk.unsupported module):

There’s also an interesting discussion regarding Spring dependencies on java.desktop:

Hopefully Spring 6 might change that…

5 Likes