{"record":{"id":"38f89de01022e0ab","repo":"chinabugotech/hutool","slug":"src-is-a-directory-but-dest-is-a-file-38f89d","errorCode":null,"errorMessage":"Src [{}] is a directory but dest [{}] is a file!","messagePattern":"Src \\[(.+?)\\] is a directory but dest \\[(.+?)\\] is a file!","errorType":"exception","errorClass":"IORuntimeException","httpStatus":null,"severity":"error","filePath":"hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java","lineNumber":219,"sourceCode":"\t * 拷贝目录内容，只用于内部，不做任何安全检查<br>\n\t * 拷贝内容的意思为源目录下的所有文件和目录拷贝到另一个目录下，而不拷贝源目录本身\n\t *\n\t * @param src 源目录\n\t * @param dest 目标目录\n\t * @throws IORuntimeException IO异常\n\t */\n\tprivate void internalCopyDirContent(File src, File dest) throws IORuntimeException {\n\t\tif (null != copyFilter && false == copyFilter.accept(src)) {\n\t\t\t//被过滤的目录跳过\n\t\t\treturn;\n\t\t}\n\n\t\tif (false == dest.exists()) {\n\t\t\t//目标为不存在路径，创建为目录\n\t\t\t//noinspection ResultOfMethodCallIgnored\n\t\t\tdest.mkdirs();\n\t\t} else if (false == dest.isDirectory()) {\n\t\t\tthrow new IORuntimeException(StrUtil.format(\"Src [{}] is a directory but dest [{}] is a file!\", src.getPath(), dest.getPath()));\n\t\t}\n\n\t\tfinal String[] files = src.list();\n\t\tif(ArrayUtil.isNotEmpty(files)){\n\t\t\tFile srcFile;\n\t\t\tFile destFile;\n\t\t\tfor (String file : files) {\n\t\t\t\tsrcFile = new File(src, file);\n\t\t\t\tdestFile = this.isOnlyCopyFile ? dest : new File(dest, file);\n\t\t\t\t// 递归复制\n\t\t\t\tif (srcFile.isDirectory()) {\n\t\t\t\t\tinternalCopyDirContent(srcFile, destFile);\n\t\t\t\t} else {\n\t\t\t\t\tinternalCopyFile(srcFile, destFile);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/chinabugotech/hutool/blob/8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java#L201-L237","documentation":"Thrown by FileCopier during recursive directory copying (internalCopyDirContent) when a source subdirectory must be copied but the corresponding destination path already exists and is a regular file rather than a directory. Hutool's FileCopier defines file-to-file, file-to-dir, and dir-to-dir as legal, but copying a directory onto a file is undefined, so it aborts. The message formats both the source and destination paths to aid diagnosis. Note the top-level copy() (line 185) has its own equivalent check with an unformatted message; this 219 variant is the recursive one that fires mid-traversal.","triggerScenarios":"Calling FileCopier.create(srcDir, dest).copy() where srcDir is a directory and dest (or a child path resolved during recursion via new File(dest, childName)) is an existing regular file. Fires when a source subdirectory name collides with an existing file name in the destination tree, or when setOnlyCopyFile(true) is combined with a destination layout where a subdir maps onto a file. Also reachable if dest was created as a file by a prior partial copy run or another process between the top-level check and the recursive call.","commonSituations":"Re-running a directory copy into a target where a previous interrupted run left a file where a directory is expected; a backup/extract target path mistakenly pointing at an existing file; concurrent writers creating a file at the destination path mid-copy; extracting an archive whose entries include a directory that conflicts with a pre-existing file of the same name; misusing isOnlyCopyFile so subdirectories collapse onto existing files.","solutions":["Point dest at a path that either does not exist or is an existing directory, so the copier can mkdir/create it correctly.","Before copying, delete or rename the conflicting file at the destination path named in the error message.","If you intended a file-to-file copy, ensure src is actually a regular file, not a directory.","Pre-clean the destination tree (or enable isOverride / remove stale files) so no directory maps onto an existing file.","Make the source and destination layout deterministic and free of name collisions before invoking copy()."],"exampleFix":"// before: dest 'logs.bak' is an existing FILE, src 'logs' is a directory\nFileCopier.create(new File(\"/data/logs\"), new File(\"/data/logs.bak\")).copy();\n// -> IORuntimeException: Src [/data/logs] is a directory but dest [/data/logs.bak] is a file!\n\n// after: target a fresh or existing directory\nFile dest = new File(\"/data/logs.bak\");\nif (dest.exists() && !dest.isDirectory()) {\n    FileUtil.del(dest); // remove the conflicting file first\n}\nFileCopier.create(new File(\"/data/logs\"), dest).copy();","handlingStrategy":"validation","validationCode":"File src = new File(srcPath);\nFile dest = new File(destPath);\nif (!src.exists()) throw new IllegalArgumentException(\"src missing: \" + src);\nif (src.isDirectory() && dest.exists() && !dest.isDirectory()) {\n    throw new IllegalStateException(\n        \"dest is an existing file but src is a directory: \" + dest);\n}\n// For recursive safety, also scan src subdirs vs existing dest files:\nif (src.isDirectory()) {\n    Path srcRoot = src.toPath();\n    Path destRoot = dest.toPath();\n    Files.walk(srcRoot).forEach(p -> {\n        if (Files.isDirectory(p)) {\n            Path rel = srcRoot.relativize(p);\n            Path candidate = destRoot.resolve(rel);\n            if (Files.exists(candidate) && !Files.isDirectory(candidate)) {\n                throw new IllegalStateException(\n                    \"Conflict: dest file blocks src dir at \" + candidate);\n            }\n        }\n    });\n}\nFileCopier.create(src, dest).copy();","typeGuard":"boolean canCopyDirToDir(File src, File dest) {\n    if (src == null || dest == null) return false;\n    if (!src.isDirectory()) return false;            // this error is dir->file\n    return !dest.exists() || dest.isDirectory();     // absent or a dir is OK\n}","tryCatchPattern":"try {\n    FileCopier.create(srcDir, dest).copy();\n} catch (IORuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"is a directory but dest\")) {\n        // resolve the conflicting file, then optionally retry\n        log.warn(\"dest file blocks dir copy: {}\", dest);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Always pass a dest that is either non-existent or a directory when src is a directory.","Pre-clean or version the destination directory before copying.","Avoid setOnlyCopyFile(true) when the destination may contain files whose names collide with source subdirectories.","Validate src.isDirectory()/dest.isDirectory() before calling copy().","Run copies against a fresh staging directory, then atomically rename into place."],"tags":["io","filesystem","file-copy","directory","hutool"],"backgroundTag":null,"analyzedSha":"8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442","analyzedAt":"2026-08-14T04:01:12.892Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}