apache/hadoop · error · IOException

Rename dir to self is forbidden

Error message

Rename dir to self is forbidden

What it means

In getDstUri, when the source is a directory the destination is normalized to directory form (UriPaths.toDirectory, adding the trailing '/') and then compared to src; if they are equal the rename is rejected because renaming a directory to itself is a forbidden no-op rather than a silent success.

Source

Thrown at hadoop-cloud-storage-project/hadoop-gcp/src/main/java/org/apache/hadoop/fs/gs/GoogleCloudStorageFileSystem.java:591

    // After applying the rules, we will be left with 2 paths such that:
    // -- either both are files or both are directories
    // -- src exists and dst leaf does not exist
    if (srcInfo.isDirectory()) {
      // -- if src is a directory
      //    -- dst is an existing file => disallowed
      //    -- dst is a directory => rename the directory.

      // 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 {

View on GitHub (pinned to 2add963021)

Solutions

  1. Skip the rename when the normalized source and destination are equal (compare UriPaths.toDirectory of both).
  2. Choose a genuinely different destination directory name.
  3. When constructing destinations, never derive dst purely from src plus formatting.

Example fix

// before
Path src = new Path("gs://bucket/foo");
Path dst = new Path("gs://bucket/foo/"); // normalized to src -> forbidden
fs.rename(src, dst);

// after
URI s = UriPaths.toDirectory(src.toUri());
URI d = UriPaths.toDirectory(dst.toUri());
if (!s.equals(d)) {
  fs.rename(src, dst);
}
Defensive patterns

Strategy: validation

Validate before calling

URI srcDir = UriPaths.toDirectory(src.toUri());
URI dstDir = UriPaths.toDirectory(dst.toUri());
if (!srcDir.equals(dstDir)) {
  fs.rename(src, dst);
}

Try / catch

catch IOException with message equals("Rename dir to self is forbidden") - treat as a no-op success or fix destination construction; do not retry.

Prevention

When it happens

Trigger: rename(gs://b/foo, gs://b/foo) or rename(gs://b/foo, gs://b/foo/) - the trailing slash is normalized away, making dst equal src. Any dst differing from src only by a trailing '/'.

Common situations: Shell-style 'mv dir dir/' habits encoded into scripts; destination built programmatically from the source path plus an optional separator; copy-pasted paths that accidentally reuse the source as destination.

Understand the failure class

Related errors


AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22). Data as JSON: /api/errors/b09a9a64f117e989. Report an issue: GitHub.