apache/hadoop · error · IOException
Rename to subdir is forbidden
Error message
Rename to subdir is forbidden
What it means
For directory renames, getDstUri computes src.relativize(dst); if the result does not equal dst, then src is a parent (ancestor) directory of dst, meaning the rename would move a directory into its own subtree. That is logically impossible (would require moving objects under themselves) and is explicitly forbidden.
Source
Thrown at hadoop-cloud-storage-project/hadoop-gcp/src/main/java/org/apache/hadoop/fs/gs/GoogleCloudStorageFileSystem.java:599
// The first case (dst is an existing file) is already checked earlier.
// If the destination path looks like a file, make it look like a
// directory path. This is because users often type 'mv foo bar'
// rather than 'mv foo bar/'.
if (!dstInfo.isDirectory()) {
dst = UriPaths.toDirectory(dst);
}
// Throw if renaming directory to self - this is forbidden
if (src.equals(dst)) {
throw new IOException("Rename dir to self is forbidden");
}
URI dstRelativeToSrc = src.relativize(dst);
// Throw if dst URI relative to src is not equal to dst,
// because this means that src is a parent directory of dst
// and src cannot be "renamed" to its subdirectory
if (!dstRelativeToSrc.equals(dst)) {
throw new IOException("Rename to subdir is forbidden");
}
if (dstInfo.exists()) {
dst =
dst.equals(GCSROOT)
? UriPaths.fromStringPathComponents(
srcItemName, /* objectName= */ null, /* allowEmptyObjectName= */ true)
: UriPaths.toDirectory(dst.resolve(srcItemName));
}
} else {
// -- src is a file
// -- dst is a file => rename the file.
// -- dst is a directory => similar to the previous case after
// appending src file-name to dst
if (dstInfo.isDirectory()) {
if (!dstInfo.exists()) {
throw new IOException("Cannot rename because path does not exist: " + dstInfo.getPath());View on GitHub (pinned to 2add963021)
Solutions
- Choose a destination outside the source subtree, e.g. gs://b/renamed-foo instead of gs://b/foo/bar.
- To move data 'into itself' semantics, copy then delete: FileUtil.copy(fs, src, fs, dst, true, conf) followed by fs.delete(src, true).
- Add a guard rejecting dst that starts with src + '/' before calling rename.
Example fix
// before
fs.rename(new Path("gs://bucket/foo"), new Path("gs://bucket/foo/bar"));
// -> IOException: Rename to subdir is forbidden
// after: destination outside the source subtree
fs.rename(new Path("gs://bucket/foo"), new Path("gs://bucket/renamed-foo")); Defensive patterns
Strategy: validation
Validate before calling
URI srcDir = UriPaths.toDirectory(src.toUri());
URI dstDir = UriPaths.toDirectory(dst.toUri());
boolean dstInsideSrc = dstDir.toString().startsWith(srcDir.toString() + "/") || dstDir.equals(srcDir);
if (!dstInsideSrc) {
fs.rename(src, dst);
} else {
// pick a destination outside the subtree, or copy + delete
} Try / catch
catch IOException with message equals("Rename to subdir is forbidden") - restructure the move: rename to a sibling path, or FileUtil.copy then delete(src, true). Prevention
- Guard 'dst starts with src + /' in every directory-rename wrapper.
- For archive/backup flows, move data to sibling directories, never to subdirectories of the source.
When it happens
Trigger: rename(gs://b/foo, gs://b/foo/bar); rename(gs://b/foo, gs://b/foo/bar/baz); any destination whose path starts with the source directory path plus '/'.
Common situations: Data reorganization code that builds the destination as sourcePath + subpath by mistake; 'move into backup subfolder' logic like rename(dir, dir/backup); relative-path arithmetic bugs when composing URIs.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Cannot rename because path does not exist: %s
- Rename dir to self is forbidden
- A file cannot be created in root.
- Cannot overwrite an existing file: %s
- Cannot create directories because of existing file: %s
AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22).
Data as JSON: /api/errors/ebaf3f8be78abd57.
Report an issue: GitHub.