opendataloader-project/opendataloader-pdf · critical · EnvironmentNotUsableException

Cannot use the temporary directory '{tempDir}'. PDF processi

Error message

Cannot use the temporary directory '{tempDir}'. PDF processing needs it to read font metrics and decode content streams. Point java.io.tmpdir or the TMPDIR environment variable at a writable directory, or grant write access to this one.

What it means

The JVM's temporary directory (java.io.tmpdir / TMPDIR) must be both writable AND deletable by the process. PDF processing spills unbounded streams to temp files (Standard-14 font metrics, embedded font programs, CMaps, decoded content streams) because a PDF stream has no size bound while memory does, so buffering cannot avoid it. validateTempDirWritable() probes by creating then deleting a temp file under Path.of(java.io.tmpdir); any IOException or RuntimeException (including InvalidPathException from a malformed -Djava.io.tmpdir) triggers TempDirectoryNotWritableException. In the CLI it is caught at CLIMain.java:264 and rethrown as EnvironmentNotUsableException (a RuntimeException) to abort the whole batch, since every remaining file would fail identically.

Source

Thrown at java/opendataloader-pdf-cli/src/main/java/org/opendataloader/pdf/cli/CLIMain.java:267

                System.out.println("Error: " + invalid.getMessage());
                return false;
            }
            LOGGER.log(Level.WARNING, invalid.getMessage() + " Skipping.");
            return true;
        } catch (InvalidPasswordException exception) {
            String password = config.getPassword();
            String message = (password == null || password.isEmpty())
                ? "Error: '" + file.getName() + "' is password-protected. Use --password option."
                : "Error: Incorrect password for '" + file.getName() + "'.";
            System.out.println(message);
            return false;
        } catch (EncryptedTaggedPdfNotSupportedException exception) {
            System.out.println("Error: " + exception.getMessage());
            return false;
        } catch (TempDirectoryNotWritableException exception) {
            // Environment failure, not a problem with this file: every remaining
            // file would fail the same way. Abort instead of repeating it per file.
            throw new EnvironmentNotUsableException(exception);
        } catch (Exception exception) {
            LOGGER.log(Level.SEVERE, "Exception during processing file " + file.getAbsolutePath() + ": " +
                exception.getMessage());
            return false;
        } finally {
            StaticContainers.closeImagesUtils();
        }
    }

    private static boolean isPdfFile(File file) {
        if (!file.isFile()) {
            return false;
        }
        String name = file.getName();
        return name.toLowerCase(Locale.ROOT).endsWith(".pdf");
    }
}

View on GitHub (pinned to a7789b8e77)

Solutions

  1. Point the temp dir at a writable location: add -Djava.io.tmpdir=/tmp to the JVM args or export TMPDIR=/tmp.
  2. In containers, mount a writable volume at /tmp (emptyDir/tmpfs in k8s, --tmpfs /tmp in Docker) or drop --read-only / readOnlyRootFilesystem.
  3. Grant write+delete permission (chmod) or ownership (chown) on the existing temp directory to the JVM user.
  4. Ensure the temp path exists and is not a malformed/relative value passed to -Djava.io.tmpdir.

Example fix

// before: docker run --read-only myimg pdf --in f.pdf
// after:  docker run --read-only --tmpfs /tmp myimg pdf --in f.pdf
// or JVM: java -Djava.io.tmpdir=/tmp -jar pdf.jar ...
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: confirm java.io.tmpdir is create+delete writable before processing.
String tempDir = System.getProperty("java.io.tmpdir");
try {
    Path probe = Files.createTempFile(Path.of(tempDir), "opendataloader-", ".probe");
    Files.delete(probe);
} catch (IOException | RuntimeException e) {
    System.err.println("Temp dir not usable: " + tempDir + " (" + e + ")");
    System.setProperty("java.io.tmpdir", "/tmp"); // or fail fast
}

Try / catch

// Public API path: TempDirectoryNotWritableException is a CHECKED IOException.
try {
    OpenDataLoaderPDF.processFile(path, config);
} catch (TempDirectoryNotWritableException e) {
    // Environment failure: stop the whole batch, do not retry per file.
    throw new IllegalStateException("Temp dir unusable; aborting batch", e);
}
// CLI path: it surfaces as the unchecked EnvironmentNotUsableException.
try { cliMain(args); }
catch (RuntimeException e) { /* EnvironmentNotUsableException is private; treat as fatal env error */ }

Prevention

When it happens

Trigger: Called from the start of OpenDataLoaderPDF.processFile, DocumentProcessor.processFile/processFileWithResult/extractContents/preprocessing, or AutoTagger.tag when System.getProperty("java.io.tmpdir") resolves to a read-only, non-deletable, or malformed path. The probe fails on create (Files.createTempFile) or on delete (Files.delete) when a sticky bit is owned by another user.

Common situations: Docker/k8s with --read-only or readOnlyRootFilesystem and no writable /tmp; restricted CI runners (GitHub Actions, sandboxed executors); SELinux/AppArmor confinement; TMPDIR exported to a deleted or unmounted directory; -Djava.io.tmpdir pointing at a path the JVM user cannot own.

Related errors


AI-assisted analysis of opendataloader-project/opendataloader-pdf@a7789b8e77 (2026-08-14). Data as JSON: /api/errors/33e2aad131fdcbc6. Report an issue: GitHub.