The Java Module System in practice

version 1.0.0

JDK Distributions

Release GA Date Oracle Azul RedHat AdoptOpenJDK Corretto

Java 8

03.2014

03.2022 (03.2025)

03.2024 (03.2026)

06.2023

09.2023

06.20231

Java 9

09.2017

03.2018

03.2020 (03.2021)

-

03.2018

-

Java 10

03.2018

09.2018

09.2018

-

09.2018

-

Java 11

09.2018

09.2023 (09.2026)

09.2028 (09.2030)

10.2024

09.2022

08.2024

Java 12

03.2019

09.2019

09.2019

-

09.2019

-

Java 13

09.2019

03.2020

03.2023 (03.2024)

-

03.2020

-

…​

Java 17

09.2021

09.2026 (09.2029)

09.2031 (09.2033)

?

?

?

1 This distribution also contains JavaFX

The Java Module System - History

The Java Module System - Basics

  • module: a named set of packages, resources, and native libraries
  • modularization: the act of decomposing a system into self-contained but interconnected modules

 

 Modularization is not mandatory.
 

 Partial modularization is also possible.

The Java Module System - Features

  • Strong encapsulation

    • a module is able to conceal parts of its code from other modules
    • clear separation between publicly usable code and internal implementation code

  • Well-defined interfaces

    • code that is not encapsulated is part of the pubic API of a module
    • modules should expose well-defined and stable interfaces to other modules

  • Explicit dependencies

    • dependencies must be part of the module definition
    • explicit dependencies give rise to a module graph
          (cyclic dependencies not allowed)

Java SE Modules (2010)

modules 2010

Java SE Modules (Java 9)

modules java9

Java SE Modules (Java 9)

modules java9 deprecated

Java SE Modules (Java 11)

modules java11

Other modules

Modules shown in red color have been removed in Java 11.

JDK modules

jdk.accessibility
jdk.attach
jdk.charsets
jdk.compiler
jdk.crypto.cryptoki
jdk.crypto.ec
jdk.dynalink
jdk.editpad
jdk.hotspot.agent

jdk.httpserver
jdk.incubator.httpclient1
jdk.jartool
jdk.javadoc
jdk.jcmd
jdk.jconsole
jdk.jdeps
jdk.jdi
jdk.jdwp.agent

jdk.jfr
jdk.jlink
jdk.jshell
jdk.jsobject
jdk.jstatd
jdk.localedata
jdk.management
jdk.management.agent
jdk.management.cmm

jdk.management.jfr
jdk.management.resource
jdk.naming.dns
jdk.naming.rmi
jdk.net
jdk.pack
jdk.packager.services
jdk.policytool2
jdk.rmic

jdk.scripting.nashorn
jdk.sctp
jdk.security.auth
jdk.security.jgss
jdk.snmp
jdk.xml.dom
jdk.zipfs

1 Promoted to Java SE as java.net.http
2 Removed in Java 10

  • JavaFX modules - removed in Java 11; available as standalone library: OpenJFX
  • Other modules: java.jnlp, java.smartcardio
Execute java --list-modules to get the full list of platform modules.

Replacements for removed modules

Module name Package Maven artifact

java.activation

javax.activation.*

com.sun.activation:javax.activation:1.2.0

java.corba

javax.activity.*
javax.rmi.*
org.omg.*

org.jboss.openjdk-orb:openjdk-orb:8.1.2.Final

java.transaction

javax.transaction.*

javax.transaction:javax.transaction-api:1.3

java.xml.bind

javax.xml.bind.*

org.glassfish.jaxb:jaxb-runtime:2.3.2

java.xml.ws

javax.xml.ws.*
javax.jws.*
javax.soap.*

com.sun.xml.ws:jaxws-ri:2.3.2

java.xml.ws.annotation

javax.annotation.*

javax.annotation:javax.annotation-api:1.3.2

Live demo - dealing with removed modules

(Code from prj-1)

Product.java (scroll to see more code)
package org.example.jpms;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Product {
    private int id;
    private String name;
    private double price; // Using double for money is actually a bad idea

    public Product() {
        this(-1, "", 0);
    }

    public Product(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return name + ": " + price;
    }
}

Live demo - dealing with removed modules

(Code from prj-1)

ProductTest.java
package org.example.jpms;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class ProductTest {
    @Test
    public void testToString() {
        Product p = new Product(33, "spaghetti", 2.15);
        Assertions.assertEquals("spaghetti: 2.15", p.toString());
    }
}

Live demo - dealing with removed modules

(Code from prj-1)

XMLPrinter.java
package org.example.jpms;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class XMLPrinter {
    public static void main(String[] args) throws Exception {
        Product product = new Product(100, "pizza", 3.25);

        JAXBContext jaxbContext = JAXBContext.newInstance(Product.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        jaxbMarshaller.marshal(product, System.out);
    }
}

Live demo - dealing with removed modules

(Code from prj-1)

Execute:
./gradlew run

With JDK 8:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product>
    <id>100</id>
    <name>pizza</name>
    <price>3.25</price>
</product>

With JDK 11:
Error: package javax.xml.bind.annotation does not exist

To fix the error, add to build.gradle:
implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.2'

Module descriptors

module info

The module descriptor specifies:

  • the module’s name (usually in reverse DNS notation)
  • the module’s dependencies
  • the packages made available to other modules
  • the services used and provided by this module

A type in one module is accessible by code
in another module only if:

  • the type is public
  • the package is exported
  • the second module reads the first

Module descriptors

 
module-info.java

module java.prefs {
    requires java.xml;

    exports java.util.prefs;
}

  • java.prefs can access types from the packages exported by java.xml
    (java.prefs reads the java.xml module)

  • java.prefs exposes its java.util.prefs package to other modules
java.prefs

Module descriptors

 
module-info.java

module java.datatransfer {
    exports java.awt.datatransfer;

    exports sun.datatransfer to java.desktop;
}

  • java.datatransfer exposes the java.awt.datatransfer package to all other modules

  • java.datatransfer exposes the sun.datatransfer package only to the java.desktop module

    • This is a qualified export

java.datatransfer

Module descriptors - implied readability

java/sql/Driver.java
package java.sql;

import java.util.logging.Logger;

public interface Driver {
    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
    ...
}

Driver exposes types from the java.logging module in its own public API.

module-info.java
module java.sql {
    requires transitive java.logging;
    ...
}

java.sql grants readability to java.logging to any module that depends upon it.

Aggregator Modules

java.se.11
module java.se {
    requires transitive java.compiler;
    requires transitive java.datatransfer;
    requires transitive java.desktop;
    requires transitive java.instrument;
    requires transitive java.logging;
    requires transitive java.management;
    ...
}

Module descriptors: services

(Code from prj-4)

  • How can a modular application use some kind of plug-ins?
  • How can a module be wired with other modules only during run-time?
public interface Compressor {
    byte[] compress(byte[] data);
    double getCompressionRatio();
}

module org.example.jpms.lzw
public class LZWCompressor
                implements Compressor {
    @Override
    public double getCompressionRatio() {
        return 0.48;
    }
    @Override
    public byte[] compress(byte[] data) {
        byte[] compressedData = null;
        // ... (LZW implementation)
        return compressedData;
    }
}
module org.example.jpms.huffman
public class HuffmanCompressor
                implements Compressor {
    @Override
    public double getCompressionRatio() {
        return 0.37;
    }
    @Override
    public byte[] compress(byte[] data) {
        byte[] compressedData = null;
        // ... (Huffman implementation)
        return compressedData;
    }
}

Module descriptors: services

(Code from prj-4)

module org.example.jpms.zip {
    exports org.example.jpms.zip;

}
public interface Compressor {
    byte[] compress(byte[] data);
    double getCompressionRatio();
}
import org.example.jpms.zip.Compressor;

module org.example.jpms.lzw {
    requires org.example.jpms.zip;
    provides Compressor with org.example.jpms.lzw.LZWCompressor;
}
import org.example.jpms.zip.Compressor;

module org.example.jpms.huffman {
    requires org.example.jpms.zip;
    provides Compressor with org.example.jpms.huffman.HuffmanCompressor;
}

Module descriptors: services

(Code from prj-4)

module org.example.jpms.zip {
    exports org.example.jpms.zip;

}
module org.example.jpms.app {
    requires org.example.jpms.zip;
    uses Compressor;
}
public interface Compressor {
    byte[] compress(byte[] data);
    double getCompressionRatio();
}
public class App {
    public static void main(String[] args) {
        for(Compressor compressor: ServiceLoader.load(Compressor.class)) {
            System.out.println(compressor.getClass().getSimpleName() +
                    " (" + compressor.getCompressionRatio() + ")");
        }
    }
}

Module descriptors: services

(Code from prj-5)

module org.example.jpms.zip {
    exports org.example.jpms.zip;
    uses Compressor;
}
module org.example.jpms.app {
    requires org.example.jpms.zip;

}
public interface Compressor {
    byte[] compress(byte[] data);
    double getCompressionRatio();

    static Iterable<Compressor> getCompressors() {
        return ServiceLoader.load(Compressor.class);
    }
}
public class App {
    public static void main(String[] args) {
        for(Compressor compressor: Compressor.getCompressors()) {
            System.out.println(compressor.getClass().getSimpleName() +
                    " (" + compressor.getCompressionRatio() + ")");
        }
    }
}

Opening modules for deep reflection

Popular tools that use reflection:

  • Spring (dependency injection)
  • JUnit (unit test execution)
  • Hibernate (persistence)
  • Copper (workflow processing)
  • JAXB (XML serialization)
  • Jackson (JSON serialization)
  • FindBugs / SpotBugs (code analysis)
  • any other tools that process annotations

A module needs to be able to:

  • provide access to internal types without exporting packages
  • allow reflective access to all parts of these types
    (exports doesn’t solve this problem)

Opening modules for deep reflection

@XmlRootElement
public class Product {
    private int id;
    private String name;
    private double price;

    ...

}
public static void main(String[] args) throws Exception {
    Product product = new Product(100, "pizza", 3.25);
    JAXBContext jaxbContext =
                    JAXBContext.newInstance(Product.class);
    Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
    jaxbMarshaller.setProperty(
                    Marshaller.JAXB_FORMATTED_OUTPUT, true);
    jaxbMarshaller.marshal(product, System.out);
}

module org.example.jpms {
    requires java.xml.bind;
    opens org.example.jpms to java.xml.bind;
}
  • It’s not necessary to export the org.example.jpms package

Opening modules for deep reflection

Making a module open
open module org.example.jpms {
    ....
}

 

Making a package open for all modules
module org.example.jpms {
    opens org.example.jpms.impl;
}

 

Qualified opens
module org.example.jpms {
    opens org.example.jpms.impl to org.example.tool;
}

VM arguments to break encapsulation

Options for the javac and java tools:

  • --add-exports module/package=other-module(,other-module)*
    Specifies a package to be considered as exported from its defining module to additional modules.
     

     
  • --add-reads module=other-module(,other-module)*
    Specifies additional modules to be considered as required by a given module.
     

     
  • --add-opens module/package=target-module(,target-module)*
    Updates module to open package to target-module, regardless of module declaration.
    (has no effect on javac)

Classpath and module path

Modules are resolved from the module path.

  • classpath: allows managing the available types only as a flat list
  • module path: allows efficient indexing based on information from the module descriptors

    • The Java runtime and the compiler know in which module to look for a given type

Specifying the module path with the javac and java tools:

--module-path modulepath…​
    or
-p modulepath…​

    modulepath: a list of directories of modules (containing exploded modules, modular jars or jmod files).

Classpath and module path

You can mix --class-path and --module-path.

  • modular jar on the module-path
  • modular jar on the classpath
  • non-modular jar on the module-path
  • non-modular jar on the classpath

All code on the classpath is part of the unnamed module, which:

  • exports all code on the classpath
  • reads all other modules
  • it is readable only from automatic modules!

Automatic modules

Non-modular jars found on the module-path are turned into automatic modules.

  • A module descriptor is generated on the fly

    • it requires transitive all other resolved modules
    • it exports all its packages
    • it reads the unnamed module

  • The name of the automatic module:

    • if present: the Automatic-Module-Name attribute of the META-INF/MANIFEST.MF file
      (highly recommended in order to prevent the module hell)
    • otherwise: derived from the jar’s name

Split packages

  • Packages are not allowed to span different modules.
        (This applies even for packages that are not exported.)
  • No classes are allowed in the default package.

Dealing with split packages using the javac and java tools:

--patch-module module=file(:file)*
    Merges all classes from a list of files into the given module.

Example:
java
  --module-path ...
  --add-modules ...
  --patch-module java.xml.ws.annotation=/path/to/my/lib/jsr305-3.0.2.jar
  --module org.example.hello/org.example.hello.HelloWorld

Live demo: Gradle without the moduleplugin

(Code from prj-2)

Several adjustments need to be made to build.gradle

To allow compilation:
compileJava {
    doFirst {
        options.compilerArgs = ['--module-path', classpath.asPath]
        classpath = files()
    }
}

To allow running:
run {
    inputs.property("moduleName", moduleName)
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', moduleName
        ]
        classpath = files()
    }
}

Live demo: Gradle without the moduleplugin

(Code from prj-2)

To allow testing:
compileTestJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'org.junit.jupiter.api',
                '--add-reads', "$moduleName=org.junit.jupiter.api",
                '--patch-module', "$moduleName=" + files(sourceSets.test.java.srcDirs).asPath,
        ]
        classpath = files()
    }
}

test {
    inputs.property("moduleName", moduleName)
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'ALL-MODULE-PATH',
                '--add-reads', "$moduleName=org.junit.jupiter.api",
                '--patch-module', "$moduleName=" + files(sourceSets.test.java.outputDir).asPath,
                '--add-opens', "$moduleName/org.example.jpms=org.junit.platform.commons"
        ]
        classpath = files()
    }
}

Live demo: Gradle without the moduleplugin

(Code from prj-2)

To fix the start scripts:
import java.util.regex.Matcher
startScripts {
    inputs.property("moduleName", moduleName)
    doFirst {
        defaultJvmOpts = [
                '--module-path', 'LIB_DIR_PLACEHOLDER',
                '--add-modules', moduleName,
        ]
    }

    doLast{
        def bashFile = new File(outputDir, applicationName)
        String bashContent = bashFile.text
        bashFile.text = bashContent.replaceFirst('LIB_DIR_PLACEHOLDER',
                                        Matcher.quoteReplacement('$APP_HOME/lib'))

        def batFile = new File(outputDir, applicationName + ".bat")
        String batContent = batFile.text
        batFile.text = batContent.replaceFirst('LIB_DIR_PLACEHOLDER',
                                        Matcher.quoteReplacement('%APP_HOME%\\lib'))
    }
}

Live demo: Gradle with the moduleplugin

(Code from prj-2)

plugins {
    id 'application'
    id "org.javamodularity.moduleplugin" version "1.4.0"
}

repositories {
    jcenter()
}

ext.moduleName = 'org.example.jpms'
mainClassName = 'org.example.jpms.XMLPrinter'

dependencies {
    implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.2'

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
}

test {
    useJUnitPlatform()
    testLogging {
        showStandardStreams = true
    }
}

Live demo: Gradle with the moduleplugin

(Code from prj-3)

Configuring additional VM arguments for testing

module-info.test
--add-modules
  java.logging

--add-reads
  org.example.jpms=java.logging

Modular jars compatible with Java 8 (or earlier)

The "standard" approach

  • from the build script start a GradleBuild task that executes gradle jar with a sourceSet that includes all classes except module-info.java.

    • this GradleBuild task runs in Java 8 (or older) compatibility mode.

  • from the build script start a GradleBuild task that executes gradle jar with a sourceSet that includes only module-info.java.

    • this GradleBuild task runs in Java 9 (or newer) compatibility mode.
    • the classpath of the java compiler needs to include the (Java 8 or older) jar assembled in the previous step.

  • merge the above two jars into a single one.

Modular jars compatible with Java 8 (or earlier)

(Code from prj-6)

The ModiTect approach

  • analyze the module-info.java file with the JavaParser
  • use the ASM bytecode manipulation framework to generate the corresponding module descriptor

Usage with the org.beryx.jar Gradle plugin

plugins {
    id 'java'
    id 'org.beryx.jar' version '1.1.3'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
Create Java 8-compatible jar
./gradlew jar
Validate the module descriptor by building with Java 11
./gradlew -PjavaCompatibility=11 jar

Multi-release jars

  • this feature allows packaging different versions of the same class file inside a single jar
  • at run-time, the JVM loads the most appropriate version
  • this feature is independent of the module system

  • the MANIFEST.MF of a multi-release jar contains the attribute Multi-Release: true
  • new versions of a class reside in the META-INF/versions/<n> directory, where <n> is a major Java version.

The org.beryx.jar plugin creates multi-release jars by default

multi release jar

How to prevent creating multi-release jars with the org.beryx.jar plugin:

jar {
    ...
    multiRelease = false
    ...
}

Other JDK tools

  • jdeps - shows the package-level or class-level dependencies of Java class files
        - can also be used to generate module-info.java files for the analyzed jars
  • jlink - assembles and optimizes a set of modules and their dependencies into a custom runtime image
        - a custom runtime image is a special distribution containing the bare minimum to run an application
        - you can create your own JRE
        - you can use jlink only if all artifacts in the dependency graph are modularized!
  • jdeprscan - scans a jar file for uses of deprecated API elements
  • jmod - creates JMOD files and lists the content of existing JMOD files
  • jimage - analyzes the lib/module file found in a custom runtime image

Custom runtime images for modular applications

The ModiTect approach:

  • for each non-modularized artifact in the dependency graph:

    • generate a module descriptor and add it to the artifact

ModiTect can be used with both Maven and Gradle.

Maven
<plugins>
  <plugin>
    <groupId>org.moditect</groupId>
    <artifactId>
      moditect-maven-plugin
    </artifactId>
	  <executions>
	  ...
	  </executions>
  </plugin>
  ...
</plugins>
Gradle
plugins{
    id "java"
    id "application"

    id "org.moditect.gradleplugin"
                       version "1.0.0-beta1"
    ...
}

moditect {
    ...
}

Custom runtime images for modular applications

The ModiTect approach (Code from prj-7)

build.gradle (scroll to see more code)
buildscript {
  repositories {
      maven {
        url "https://plugins.gradle.org/m2/"
      }
      maven {
        url 'https://jitpack.io'
      }
  }
  dependencies {
    classpath "org.moditect:moditect-gradle-plugin:1.0.0-beta1"
  }
}

plugins{
    id "java"
    id "application"
}
apply plugin: "org.moditect.gradleplugin"

repositories {
    mavenCentral()
}

group = "org.moditect"
version = "2.0.0"

targetCompatibility = JavaVersion.VERSION_1_9
sourceCompatibility = JavaVersion.VERSION_1_9

ext {
    moduleName = 'com.example'

    vertxVersion = '3.5.0'
    nettyVersion = '4.1.15.Final'
    jacksonVersion ='2.9.0'
}

mainClassName = 'com.example.HelloWorldServer'
jar {
    manifest {
        attributes("Automatic-Module-Name": moduleName)
    }
}

dependencies{
    compile "io.vertx:vertx-core:$vertxVersion"
}

moditect {
    addMainModuleInfo {
        version = project.version
        overwriteExistingFiles = false
        jdepsExtraArgs = ['-q']
        module {
            mainClass = mainClassName
            moduleInfo {
                name = moduleName
                exports = 'com.example to io.vertx.core;'
            }
        }
    }
    addDependenciesModuleInfo {
        jdepsExtraArgs = ['-q']
        modules {
            module {
                artifact "com.fasterxml.jackson.core:jackson-core:$jacksonVersion"
                moduleInfo {
                    name = 'com.fasterxml.jackson.core'
                }
            }
            module {
                artifact "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion"
                moduleInfo {
                    name = 'com.fasterxml.jackson.annotations'
                }
            }
            module {
                artifact "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
                moduleInfo {
                    name = 'com.fasterxml.jackson.databind'
                }
            }
            module {
                artifact "io.netty:netty-common:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.common'
                }
            }
            module {
                artifact "io.netty:netty-buffer:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.buffer'
                }
            }
            module {
                artifact "io.netty:netty-codec:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.codec'
                }
            }
            module {
                artifact "io.netty:netty-resolver:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.resolver'
                }
            }
            module {
                artifact "io.netty:netty-transport:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.transport'
                }
            }
            module {
                artifact "io.netty:netty-codec-dns:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.codec.dns'
                }
            }
            module {
                artifact "io.netty:netty-codec-http2:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.codec.http2'
                }
            }
            module {
                artifact "io.netty:netty-resolver-dns:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.resolver.dns'
                }
            }
            module {
                artifact "io.netty:netty-transport-native-unix-common:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.channel.unix'
                }
            }
            module {
                artifact "io.netty:netty-transport-native-epoll:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.channel.epoll'
                }

            }
            module {
                artifact "io.netty:netty-transport-native-kqueue:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.channel.kqueue'
                }
            }
            module {
                artifact "io.netty:netty-handler:$nettyVersion"
                moduleInfoSource = '''
                    module io.netty.handler {
                        exports io.netty.handler.flow;
                        exports io.netty.handler.flush;
                        exports io.netty.handler.ipfilter;
                        exports io.netty.handler.logging;
                        exports io.netty.handler.ssl;
                        exports io.netty.handler.ssl.ocsp;
                        exports io.netty.handler.ssl.util;
                        exports io.netty.handler.stream;
                        exports io.netty.handler.timeout;
                        exports io.netty.handler.traffic;
                    }
                '''
            }
            module {
                artifact "io.netty:netty-codec-socks:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.codec.socks'
                }
            }
            module {
                artifact "io.netty:netty-handler-proxy:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.handler.proxy'
                }
            }
            module {
                artifact "io.netty:netty-codec-http:$nettyVersion"
                moduleInfo {
                    name = 'io.netty.codec.http'
                }
            }
            module {
                artifact "io.vertx:vertx-core:$vertxVersion"
                moduleInfo {
                    name = 'io.vertx.core'
                    requires = '''
                        static log4j.api;
                        static log4j;
                        static slf4j.api;
                        *;
                    '''
                    exports = '''
                        !*impl*;
                        *;
                    '''
                    uses = '''
                        io.vertx.core.spi.VertxFactory;
                        io.vertx.core.spi.VerticleFactory;
                        io.vertx.core.spi.FutureFactory;
                        io.vertx.core.spi.BufferFactory;
                    '''
                }
            }
        }
    }
    createRuntimeImage {
        outputDirectory = file("$buildDir/jlink-image")
        modules = ['com.example']
        launcher {
            name = 'helloWorld'
            module = 'com.example'
        }
        compression = 2
        stripDebug = true
    }
}

Custom runtime images for modular applications

The "badass" approach

  • Combine all non-modular artifacts into a merged module.
  • Modularize the merged module.
  • Create delegating modules for each artifact contained in the merged module.

 

This approach is taken by the badass-jlink Gradle plugin.

  • You can let the plugin generate a module descriptor for the merged module.
  • Or you can use the module descriptor suggested by the plugin as a starting point for a customized module descriptor.
  • The plugin doesn’t use jdeps to generate the module descriptor, because jdeps has some limitations and bugs.

Custom runtime images for modular applications

merging

Custom runtime images for modular applications

merging.cycle

Custom runtime images for modular applications

merging.no cycle

Custom runtime images for modular applications

(Code from prj-8)

build.gradle for the "badass" approach
plugins {
    id 'application'
    id "org.javamodularity.moduleplugin" version "1.4.0"
    id 'org.beryx.jlink' version '2.5.0'
}
repositories {
    jcenter()
}
dependencies {
    implementation "io.vertx:vertx-core:3.5.0"
}
ext.moduleName = 'com.example'
mainClassName = "com.example/com.example.HelloWorldServer"

jlink {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    mergedModule {
        additive = true
        uses 'io.vertx.core.spi.VertxFactory'
        uses 'io.vertx.core.spi.VerticleFactory'
        uses 'io.vertx.core.spi.FutureFactory'
        uses 'io.vertx.core.spi.BufferFactory'
    }
}

Custom runtime images for non-modular applications

Approach:

  • create a custom runtime image containing only the JDK modules required by the application
  • use the Gradle Application Plugin to build your application
  • add your application to the custom Java runtime image

This approach is taken by the badass-runtime Gradle plugin.

Custom runtime images for non-modular applications

(Code from prj-9)

build.gradle
plugins {
    id 'application'
    id "org.beryx.runtime" version "1.1.5"
}

repositories {
    jcenter()
}

dependencies {
    implementation "io.vertx:vertx-core:3.5.0"
}

mainClassName = "com.example.HelloWorldServer"

runtime {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    modules = ['java.naming', 'java.compiler', 'java.logging', 'jdk.unsupported']
}

List of useful plugins