alibaba/arthas · error · IOException

File '{file}' exists but is a directory

Error message

File '{file}' exists but is a directory

What it means

FileUtils.openOutputStream(file, append) checks the existing target: if file.exists() && file.isDirectory(), it throws IOException("File '<file>' exists but is a directory"). You cannot open a FileOutputStream on a directory entry.

Source

Thrown at common/src/main/java/com/taobao/arthas/common/FileUtils.java:116

	 * The parent directory will be created if it does not exist. The file will be
	 * created if it does not exist. An exception is thrown if the file object
	 * exists but is a directory. An exception is thrown if the file exists but
	 * cannot be written to. An exception is thrown if the parent directory cannot
	 * be created.
	 *
	 * @param file   the file to open for output, must not be {@code null}
	 * @param append if {@code true}, then bytes will be added to the end of the
	 *               file rather than overwriting
	 * @return a new {@link FileOutputStream} for the specified file
	 * @throws IOException if the file object is a directory
	 * @throws IOException if the file cannot be written to
	 * @throws IOException if a parent directory needs creating but that fails
	 * @since 2.1
	 */
	public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
		if (file.exists()) {
			if (file.isDirectory()) {
				throw new IOException("File '" + file + "' exists but is a directory");
			}
			if (!file.canWrite()) {
				throw new IOException("File '" + file + "' cannot be written to");
			}
		} else {
			final File parent = file.getParentFile();
			if (parent != null) {
				if (!parent.mkdirs() && !parent.isDirectory()) {
					throw new IOException("Directory '" + parent + "' could not be created");
				}
			}
		}
		return new FileOutputStream(file, append);
	}
	
    /**
     * Reads the contents of a file into a byte array.
     * The file is always closed.

View on GitHub (pinned to 21cf2e9ba5)

Solutions

  1. Append a filename to the path so it targets a file, not a directory.
  2. Validate file.isFile() (or !isDirectory()) before calling openOutputStream.
  3. If writing many files, loop over names inside the directory rather than writing to the directory itself.

Example fix

// before
FileUtils.openOutputStream(new File("/var/log/arthas"), false);  // /var/log/arthas is a dir -> throws

// after
FileUtils.openOutputStream(new File("/var/log/arthas", "trace.log"), false);
Defensive patterns

Strategy: validation

Validate before calling

if (file.exists() && file.isDirectory()) {
    throw new IOException("target is a directory: " + file);
}

Type guard

static boolean isWritableFile(File f) {
    return f != null && (!f.exists() || f.isFile());
}

Try / catch

try {
    return FileUtils.openOutputStream(file, append);
} catch (IOException e) {
    if (e.getMessage().endsWith("is a directory")) {
        // append a filename or pick a different path
    } else throw e;
}

Prevention

When it happens

Trigger: Passing a File path that resolves to an existing directory (e.g. output path equals a folder, or a missing filename so the path collapses to a directory).

Common situations: Output path config omitting the filename (path ends at a directory); a path collision where a log/output dir shares a name with the intended file; user supplied a directory where a file path was expected.

Related errors


AI-assisted analysis of alibaba/arthas@21cf2e9ba5 (2026-08-14). Data as JSON: /api/errors/38af081d7ae97018. Report an issue: GitHub.