{"record":{"id":"ebaf3f8be78abd57","repo":"apache/hadoop","slug":"rename-to-subdir-is-forbidden","errorCode":null,"errorMessage":"Rename to subdir is forbidden","messagePattern":"Rename to subdir is forbidden","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-cloud-storage-project/hadoop-gcp/src/main/java/org/apache/hadoop/fs/gs/GoogleCloudStorageFileSystem.java","lineNumber":599,"sourceCode":"      // The first case (dst is an existing file) is already checked earlier.\n      // If the destination path looks like a file, make it look like a\n      // directory path. This is because users often type 'mv foo bar'\n      // rather than 'mv foo bar/'.\n      if (!dstInfo.isDirectory()) {\n        dst = UriPaths.toDirectory(dst);\n      }\n\n      // Throw if renaming directory to self - this is forbidden\n      if (src.equals(dst)) {\n        throw new IOException(\"Rename dir to self is forbidden\");\n      }\n\n      URI dstRelativeToSrc = src.relativize(dst);\n      // Throw if dst URI relative to src is not equal to dst,\n      // because this means that src is a parent directory of dst\n      // and src cannot be \"renamed\" to its subdirectory\n      if (!dstRelativeToSrc.equals(dst)) {\n        throw new IOException(\"Rename to subdir is forbidden\");\n      }\n\n      if (dstInfo.exists()) {\n        dst =\n            dst.equals(GCSROOT)\n                ? UriPaths.fromStringPathComponents(\n                srcItemName, /* objectName= */ null, /* allowEmptyObjectName= */ true)\n                : UriPaths.toDirectory(dst.resolve(srcItemName));\n      }\n    } else {\n      // -- src is a file\n      //    -- dst is a file => rename the file.\n      //    -- dst is a directory => similar to the previous case after\n      //                             appending src file-name to dst\n\n      if (dstInfo.isDirectory()) {\n        if (!dstInfo.exists()) {\n          throw new IOException(\"Cannot rename because path does not exist: \" + dstInfo.getPath());","sourceCodeStart":581,"sourceCodeEnd":617,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-cloud-storage-project/hadoop-gcp/src/main/java/org/apache/hadoop/fs/gs/GoogleCloudStorageFileSystem.java#L581-L617","documentation":"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.","triggerScenarios":"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 '/'.","commonSituations":"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.","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."],"exampleFix":"// before\nfs.rename(new Path(\"gs://bucket/foo\"), new Path(\"gs://bucket/foo/bar\"));\n// -> IOException: Rename to subdir is forbidden\n\n// after: destination outside the source subtree\nfs.rename(new Path(\"gs://bucket/foo\"), new Path(\"gs://bucket/renamed-foo\"));","handlingStrategy":"validation","validationCode":"URI srcDir = UriPaths.toDirectory(src.toUri());\nURI dstDir = UriPaths.toDirectory(dst.toUri());\nboolean dstInsideSrc = dstDir.toString().startsWith(srcDir.toString() + \"/\") || dstDir.equals(srcDir);\nif (!dstInsideSrc) {\n  fs.rename(src, dst);\n} else {\n  // pick a destination outside the subtree, or copy + delete\n}","typeGuard":null,"tryCatchPattern":"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).","preventionTips":["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."],"tags":["rename","gcs","hadoop","directories","path-validation"],"backgroundTag":"rename-into-subdirectory","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}