quarkusio/quarkus · error · IllegalStateException

Module of jlink image launcher is not in a module layer

Error message

Module of jlink image launcher is not in a module layer

What it means

After confirming module mode, JLinkAppLauncher.run retrieves the ModuleLayer of the launcher's module via myModule.getLayer(). A null layer means the module was defined but not bound to any module layer (e.g. loaded reflectively or via a non-standard boot), so ModuleLoader.forLayer cannot enumerate/load modules and the launcher aborts.

Source

Thrown at extensions/packaging/jlink/launcher/src/main/java/io/quarkus/jlink/launcher/JLinkAppLauncher.java:32

 * The application launcher for a jlink'd module application.
 */
public final class JLinkAppLauncher {
    private JLinkAppLauncher() {
    }

    /**
     * Launch the application.
     *
     * @param args the application arguments
     */
    public static void run(String appModule, String[] args) {
        Module myModule = JLinkAppLauncher.class.getModule();
        if (myModule == null) {
            throw new IllegalStateException("Must launch jlink image in module mode only");
        }
        ModuleLayer myLayer = myModule.getLayer();
        if (myLayer == null) {
            throw new IllegalStateException("Module of jlink image launcher is not in a module layer");
        }

        @SuppressWarnings("resource")
        ModuleLoader base = ModuleLoader.forLayer("base", myLayer);

        // try to ascertain our own path
        String cmd = ProcessHandle.current().info().command()
                .orElseThrow(() -> new IllegalStateException("Cannot determine image path (ProcessHandle)"));
        Path cmdPath = Path.of(cmd);
        Path binPath = cmdPath.getParent();
        if (binPath == null || !binPath.getFileName().toString().equals("bin")) {
            throw new IllegalStateException("Cannot determine image path (bin path)");
        }
        Path imagePath = binPath.getParent();
        if (imagePath == null) {
            throw new IllegalStateException("Cannot determine image path (image path)");
        }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Launch the application normally through the generated jlink image launcher so the boot layer contains the launcher module
  2. Do not load JLinkAppLauncher via reflection or custom classloaders outside a ModuleLayer
  3. Check for agents/tools that redefine modules and disable them for the launcher
  4. Verify the JVM is started normally (boot layer intact), not via low-level ModuleLayer APIs

Example fix

// before: reflective load breaks layer binding
Class.forName("io.quarkus.jlink.launcher.JLinkAppLauncher")
// after: exec the image launcher
Process p = new ProcessBuilder("image/bin/myapp").start();
Defensive patterns

Strategy: type-guard

Validate before calling

Module m = JLinkAppLauncher.class.getModule();
if (m == null || m.getLayer() == null) {
    throw new IllegalStateException("Launcher module is not bound to a ModuleLayer");
}

Type guard

boolean hasModuleLayer(Class<?> c) {
    Module m = c.getModule();
    return m != null && m.getLayer() != null;
}

Try / catch

try {
    JLinkAppLauncher.run(appModule, args);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("not in a module layer")) {
        throw new IllegalStateException("Launcher loaded outside a ModuleLayer; start the image normally", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: The launcher class's module has no associated ModuleLayer — typically when the class is loaded by a custom/reflective loader without layer definition, or before/atypical layer bootstrapping.

Common situations: Embedding the launcher class in a custom framework that defines modules without layers; exotic classloading setups in test harnesses; launching with instrumentation agents that redefine module loading.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/bc955c25058dfe08. Report an issue: GitHub.