quarkusio/quarkus · error · IllegalStateException

Cannot determine image path (bin path)

Error message

Cannot determine image path (bin path)

What it means

The launcher derives the jlink image root by taking the current process command, expecting its parent directory to be named 'bin' (the standard image layout image/bin/<launcher>). If ProcessHandle returns a command whose parent is missing or not named 'bin', the image path cannot be derived. Note the source also throws a distinct variant 'Cannot determine image path (ProcessHandle)' when the command itself is unavailable.

Source

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

        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)");
        }

        Path libPath = imagePath.resolve("lib").resolve("quarkus");

        // create a layer for our dynamic modules
        ModuleLoader dyn = new ModuleLoader("dyn", ModuleFinder.fromFileSystem(List.of(libPath))) {
            public LoadedModule loadModule(final String moduleName) {
                LoadedModule found = base.loadModule(moduleName);
                if (found == null) {
                    found = super.loadModule(moduleName);
                }
                return found;
            }
        };

View on GitHub (pinned to e1c734241f)

Solutions

  1. Run the launcher from the image's bin/ directory via its original path (image/bin/<name>)
  2. Avoid symlinking or moving the launcher binary; symlink the image root instead so layout stays image/bin/<launcher>
  3. Keep the standard jlink image layout (bin/ as direct parent of the executable)
  4. Check ProcessHandle command output if in a container and ensure the executable path is preserved

Example fix

// before: moved/wrapped launcher
/usr/local/bin/myapp-launcher.sh
// after: invoke in place
/opt/myapp-image/bin/myapp
Defensive patterns

Strategy: validation

Validate before calling

Path cmd = ProcessHandle.current().info().command()
        .map(Path::of).orElse(null);
if (cmd == null || cmd.getParent() == null
        || !"bin".equals(cmd.getParent().getFileName().toString())) {
    throw new IllegalStateException("Launcher must reside at <image>/bin/<name>");
}

Type guard

boolean isInsideImageBin(Path executable) {
    Path parent = executable.getParent();
    return parent != null && "bin".equals(parent.getFileName().toString());
}

Try / catch

try {
    JLinkAppLauncher.run(appModule, args);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("image path")) {
        throw new IllegalStateException("Run the launcher from the image's bin/ directory", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: The launcher binary is invoked via a symlink, wrapper script, or a path whose parent directory is not literally named 'bin'; the process command is an interpreter or relative path so getParent() is not the image bin directory.

Common situations: Copying the launcher out of bin/ to run it elsewhere; invoking through a shell wrapper in a different directory; container images that flatten or rename the bin directory; running the classpath test harness that simulates the launcher.

Related errors


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