quarkusio/quarkus · error · UncheckedIOException

Unable to set tmp java.home for FontConfig Quarkus AWT usage

Error message

Unable to set tmp java.home for FontConfig Quarkus AWT usage in ${javaHome}

What it means

On native image, Quarkus AWT's substitution for sun.awt.FontConfiguration (Linux) creates a temporary java.home layout (lib/ and conf/fonts directories) that FontConfig requires. If creating these directories fails with IOException, an UncheckedIOException with this message is thrown, aborting font initialization.

Source

Thrown at extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/JDKSubstitutions.java:69

 */
@TargetClass(className = "sun.awt.FontConfiguration", onlyWith = IsLinux.class)
final class Target_sun_awt_FontConfiguration_Linux {
    @Alias
    protected static String osVersion;
    @Alias
    protected static String osName;

    @Substitute
    protected void setOsNameAndVersion() {
        final Path javaHome = Path.of(System.getProperty("java.io.tmpdir"), "quarkus-awt-tmp-fonts");
        try {
            System.setProperty("java.home", javaHome.toString());
            osName = System.getProperty("os.name", "unknown");
            osVersion = System.getProperty("os.version");
            Files.createDirectories(javaHome.resolve("lib"));
            Files.createDirectories(javaHome.resolve("conf").resolve("fonts"));
        } catch (IOException e) {
            throw new UncheckedIOException("Unable to set tmp java.home for FontConfig Quarkus AWT usage in " + javaHome, e);
        }
    }
}

/**
 * See Target_sun_awt_FontConfiguration_Linux, for context.
 *
 * Windows doesn't have fontconfig package and its config file installed like Linux has.
 * Java runtime looks for the config file inside our fake JAVA_HOME.
 * We provide a skeleton, i18n ignorant version to satisfy the basic headless fonts processing.
 */
@TargetClass(className = "sun.awt.FontConfiguration", onlyWith = IsWindows.class)
final class Target_sun_awt_FontConfiguration_Windows {
    @Alias
    protected static String osVersion;
    @Alias
    protected static String osName;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Ensure the directory backing java.io.tmpdir (or the path reported) is writable by the process user
  2. Mount a writable tmpfs at /tmp in the container (e.g. docker run --tmpfs /tmp:rw)
  3. Check disk space and permissions; run the binary with a user that owns its directory
  4. If java.home was mis-resolved, verify how the native binary locates its temp dir and pass -Djava.io.tmpdir to a writable path

Example fix

// before (read-only rootfs)
docker run --read-only myapp-image
// after
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid myapp-image
Defensive patterns

Strategy: validation

Validate before calling

Path tmp = Path.of(System.getProperty("java.io.tmpdir"));
if (!Files.isWritable(tmp)) {
    throw new IllegalStateException("Writable temp dir required for AWT/FontConfig in native mode: " + tmp);
}

Try / catch

try {
    BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = img.createGraphics();
} catch (UncheckedIOException e) {
    if (e.getMessage() != null && e.getMessage().contains("Unable to set tmp java.home")) {
        log.error("Mount a writable /tmp or grant write access: " + e.getCause());
    }
    throw e;
}

Prevention

When it happens

Trigger: First AWT/Font usage in a native executable where the temp directory (usually io.tmpdir under the app dir) cannot be created: read-only filesystem, permission denied, or no space.

Common situations: Running the native binary in a read-only container (distroless, read-only /tmp); running as a non-root user without write access; security-hardened runtime blocking directory creation.

Related errors


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