Creating a Custom JRE for your Java Applications

Introduction

Using custom JREs (Java Runtime Environment) tailored to specific applications, instead of using the full JDK (Java Development Kit) or a larger JRE, offers several advantages.
Here's why every team should consider creating their custom JRE:

  1. Reduced Image Size:

    • One of the most immediate benefits is a significant reduction in the image size, especially when used with containerized applications. A smaller image means faster deployments, quicker scaling, and reduced storage costs.

    • A smaller image size is particularly beneficial in cloud environments where bandwidth, storage, and startup times are crucial.

  2. Faster Start-up Time:

    • Custom JREs only load the modules they need, leading to faster application start-up times, which is especially important for microservices architectures and serverless functions.
  3. Security:

    • Minimizing the runtime surface can reduce the security risk. Fewer components in the JRE mean fewer potential vulnerabilities.

    • Regularly updating a custom JRE can be simpler, ensuring that you're always using the most secure version of the modules you need.

  4. Optimized Resource Utilization:

    • A smaller runtime uses less memory and other system resources, which can result in cost savings, especially in cloud environments where you're billed for resource consumption.
  5. Simplified Dependency Management:

    • By being explicit about which modules you're using, you can simplify dependency management and avoid potential conflicts or issues from unexpected updates in unused parts of a larger JRE.
  6. Improved Portability:

    • Custom JREs can be tailored for specific platforms, ensuring optimal performance and compatibility wherever the application is deployed.
  7. Version Consistency:

    • Teams can ensure that all microservices or components are using the exact same version of the JRE, which can eliminate subtle bugs or inconsistencies arising from different services using different JRE versions.
  8. Better Understanding of Dependencies:

    • The process of creating a custom JRE can give development teams a deeper understanding of their application's dependencies, potentially identifying areas where the application can be optimized further.
  9. Tailored to Needs:

    • Not all applications need every module provided by the JDK. By trimming down the JRE, you're ensuring your applications have precisely what they need and nothing more.

However, while there are many benefits, there are also considerations:

  • Maintenance Overhead: Creating and maintaining custom JREs does introduce some overhead. Teams need to ensure they keep their custom JREs up to date with security patches and updates.

  • Testing: With a custom JRE, thorough testing is paramount. Teams must ensure they haven't omitted crucial modules or made changes that affect the application's functionality.

Java's jlink tool offers the creation of custom JREs tailored to specific applications. This process can substantially reduce the image size of Java applications.
This guide illustrates how to create a custom JRE for Java services, including Java EE applications deployed in JBoss and Spring Boot applications with embedded Tomcat server.

Prerequisites

  • Java 9 or later.

  • A Spring Boot or Java EE application.

  • Docker (if building Docker images).

  • Tomcat, JBoss or any application server

Step-by-Step Guide

  1. Analyze Dependencies with jdeps: jdeps is a dependency analysis tool.

     jdeps --print-module-deps path/to/your/app.jar
    

    This will give a list of modules your application needs.

    If you get a lot "not found" modules which are external modules, then run the jdeps command with --ignore-missing-deps flag. This should give you a clearer result.

  2. Create the Custom JRE using jlink:

     jlink --no-header-files
      --no-man-pages
      --compress=2
      --strip-debug
      --add-modules java.base,java.sql,java.logging,...
      --output customjre
    
  3. Integrate with Docker

    For non-JBoss services:

    ```java FROM openjdk:17 as jre-builder WORKDIR /jre-builder RUN jlink --no-header-files \ --no-man-pages --compress=2 \ --strip-debug \ --add-modules java.base,java.sql,java.logging,... \ --output custom-jre

Use a lightweight base image for your final image

FROM debian:bullseye-slim WORKDIR /app

Copy the custom JRE

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

Copy the jar file from the build stage

COPY --from=build /app/target/your-app-name.jar /app.jar

EXPOSE 8080

Run the application

ENTRYPOINT ["/opt/custom-jre/bin/java", "-jar", "/app.jar"] ```

  1. Iterative Testing: Errors like ClassNotFoundException suggest missing dependencies. If they arise:

    • Identify the missing module.

    • Re-run the jlink command to include the additional module.

    • Test again.

  2. Document the Process:

    • Maintain a record of the modules required for each service.

Best Practices & Tips:

  • Test applications thoroughly after applying a custom JRE.

  • While storage is relatively cheap, reducing image size provides faster deployments and start-ups, especially in containerized environments.

  • Regularly update the base image and regenerate the custom JRE to keep up with security patches.

Conclusion:

Using a custom JRE can optimize your Spring Boot and Java EE services for various deployment environments.
By reducing image sizes, you achieve faster deployments and better resource utilization. Always test thoroughly and keep the JRE updated.

Did you find this article valuable?

Support Adrian Kodja by becoming a sponsor. Any amount is appreciated!