flowable/flowable-engine · error · FlowableException

Couldn't read file ${filePath}: ${e.getMessage()}

Error message

Couldn't read file ${filePath}: ${e.getMessage()}

What it means

IoUtil.readFileAsString reads a whole file into a String and wraps any exception in FlowableException 'Couldn't read file <path>: <message>'. Note it first calls getFile(path), so a file that cannot be located via the classloader fails earlier in getFile (error 1819).

Source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/IoUtil.java:57

            int bytesRead = inputStream.read(buffer);
            while (bytesRead != -1) {
                outputStream.write(buffer, 0, bytesRead);
                bytesRead = inputStream.read(buffer);
            }
        } catch (Exception e) {
            throw new FlowableException("couldn't read input stream " + inputStreamName, e);
        }
        return outputStream.toByteArray();
    }

    public static String readFileAsString(String filePath) {
        byte[] buffer = new byte[(int) getFile(filePath).length()];
        BufferedInputStream inputStream = null;
        try {
            inputStream = new BufferedInputStream(new FileInputStream(getFile(filePath)));
            inputStream.read(buffer);
        } catch (Exception e) {
            throw new FlowableException("Couldn't read file " + filePath + ": " + e.getMessage());
        } finally {
            IoUtil.closeSilently(inputStream);
        }
        return new String(buffer);
    }

    public static File getFile(String filePath) {
        URL url = IoUtil.class.getClassLoader().getResource(filePath);
        try {
            return new File(url.toURI());
        } catch (Exception e) {
            throw new FlowableException("Couldn't get file " + filePath + ": " + e.getMessage());
        }
    }

    public static void writeStringToFile(String content, String filePath) {
        BufferedOutputStream outputStream = null;
        try {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Check the embedded cause message for the concrete IO error
  2. Verify the file exists on the filesystem (getFile resolves classloader resources — files inside jars are not plain Files)
  3. Fix filesystem permissions on the file/directory
  4. Prefer getResourceAsStream-based reading for classpath resources inside archives

Example fix

// before
String content = IoUtil.readFileAsString("config/process.xml");
// after
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config/process.xml")) {
    String content = IoUtil.readInputStream(in, "config/process.xml").toString();
}
Defensive patterns

Strategy: try-catch

Validate before calling

File f = new File(filePath);
if (!f.isFile() || !f.canRead()) {
    throw new IllegalStateException("Cannot read file: " + filePath);
}

Try / catch

try {
    String content = IoUtil.readFileAsString(filePath);
} catch (FlowableException e) {
    logger.error("File read failed for {}: {}", filePath, e.getMessage());
}

Prevention

When it happens

Trigger: Calling IoUtil.readFileAsString for a path that resolves via classloader but cannot be opened/read: unreadable permissions, file removed between lookup and read, or IO error during read.

Common situations: Reading config/resource files bundled with the app; files inside jars/WARs where File-based access breaks; permission changes in containerized deployments.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/1b86d40786230a01. Report an issue: GitHub.