opendataloader-project/opendataloader-pdf · error · TempDirectoryNotWritableException

Cannot use the temporary directory '{}'. PDF processing need

Error message

Cannot use the temporary directory '{}'. 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

TempDirectoryNotWritableException (IOException subtype) thrown by validateTempDirWritable, which runs per-file before processing. It probes the JVM temp dir by creating AND deleting a file (Files.createTempFile + Files.delete), because veraPDF spills Standard-14 font metrics, embedded font programs, CMaps, and decoded content streams to disk and then deletes them — a dir that allows creation but blocks deletion would leak one file per stream. The check prevents a silent failure mode where veraPDF logs the I/O error at FINE, swallows it, and processing 'succeeds' (exit 0) while dropping most text or throwing an unrelated NPE deep inside.

Source

Thrown at java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/processors/DocumentProcessor.java:742

     *         cannot be written to
     */
    private static void validateTempDirWritable() throws TempDirectoryNotWritableException {
        String tempDir = System.getProperty("java.io.tmpdir");
        try {
            // Resolve java.io.tmpdir explicitly rather than calling the no-arg
            // Files.createTempFile: that one resolves the directory once when the
            // JVM starts, so it would ignore a -Djava.io.tmpdir override applied
            // later and probe a different directory than veraPDF ends up using.
            // RuntimeException covers InvalidPathException from a malformed override.
            Path probe = Files.createTempFile(Path.of(tempDir), "opendataloader-", ".probe");
            // Deleting is part of what is being verified, not just cleanup: veraPDF
            // removes its spill files, so a directory that allows creation but
            // refuses deletion (a sticky bit the process does not own, say) would
            // accumulate one file per stream. Failing here also keeps the probe
            // itself from leaking.
            Files.delete(probe);
        } catch (IOException | RuntimeException e) {
            throw new TempDirectoryNotWritableException(
                "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.", e);
        }
    }

    /**
     * Path.getFileName() returns null for filesystem roots (e.g. {@code C:\}).
     * Fall back to the original input string in that case so the user-facing
     * error message is never empty.
     */
    private static String displayName(String pdfName) {
        Path fileName = Path.of(pdfName).getFileName();
        return fileName != null ? fileName.toString() : pdfName;
    }

    private static int indexOfBytes(byte[] haystack, byte[] needle) {

View on GitHub (pinned to a7789b8e77)

Solutions

  1. Point java.io.tmpdir (or TMPDIR) at a known-writable directory: run with -Djava.io.tmpdir=/var/tmp/odl or export TMPDIR=/var/tmp/odl after creating it.
  2. Grant write+delete permission on the existing temp dir to the running user (chmod/chown), or remove the sticky-bit ownership mismatch.
  3. Freeze disk space: check df -h on the temp dir's mount and clear space if full.
  4. In containers, mount an emptyDir/writable volume at /tmp or set TMPDIR to a writable emptyDir.

Example fix

# before: container runs with read-only /tmp
$ odl-pdf doc.pdf
-> TempDirectoryNotWritableException: Cannot use the temporary directory '/tmp'...
# after: override to a writable dir
$ mkdir -p /var/tmp/odl && TMPDIR=/var/tmp/odl odl-pdf doc.pdf
Defensive patterns

Strategy: validation

Validate before calling

// Probe the temp dir yourself before processing, mirroring the library's check:
Path tmp = Path.of(System.getProperty("java.io.tmpdir"));
Path probe = Files.createTempFile(tmp, "pre-", ".probe");
Files.delete(probe); // must also succeed

Type guard

static boolean isTempDirProblem(IOException e) {
    return e instanceof TempDirectoryNotWritableException;
}

Try / catch

try {
    DocumentProcessor.extractContents(pdfName, config);
} catch (TempDirectoryNotWritableException e) {
    // Environment issue, not a bad file — fix the dir, not the input.
    String tmp = System.getProperty("java.io.tmpdir");
    log.error("Temp dir {} not writable; set -Djava.io.tmpdir or TMPDIR", tmp, e);
    throw new RuntimeException("Fix java.io.tmpdir then retry", e);
}

Prevention

When it happens

Trigger: validateTempDirWritable is called at the start of updateStaticContainers/extractContents: Files.createTempFile(Path.of(tempDir), ...) or the subsequent Files.delete(probe) throws IOException (permission denied, no space, read-only mount) or RuntimeException (InvalidPathException from a malformed -Djava.io.tmpdir value).

Common situations: Container/image with a read-only or nodev /tmp. A malformed -Djava.io.tmpdir=/bad:|path override. TMPDIR env var pointing at a deleted/unwritable dir. Disk full. A sticky-bit /tmp the process does not own (allows create, blocks delete). Read-only root filesystem in a hardened pod.

Related errors


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