chinabugotech/hutool · error · IORuntimeException

Dest is a sub directory of src !

Error message

Dest is a sub directory of src !

What it means

FileCopier.copy() detects when dest is a subdirectory of src via FileUtil.isSub. Copying a directory into its own descendant would recurse infinitely (the copy keeps finding newly created children), so it throws IORuntimeException("Dest is a sub directory of src !").

Source

Thrown at hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java:188

		final File src = this.src;
		File dest = this.dest;
		// check
		Assert.notNull(src, "Source File is null !");
		if (false == src.exists()) {
			throw new IORuntimeException("File not exist: " + src);
		}
		Assert.notNull(dest, "Destination File or directiory is null !");
		if (FileUtil.equals(src, dest)) {
			throw new IORuntimeException("Files '{}' and '{}' are equal", src, dest);
		}

		if (src.isDirectory()) {// 复制目录
			if(dest.exists() && false == dest.isDirectory()) {
				//源为目录,目标为文件,抛出IO异常
				throw new IORuntimeException("Src is a directory but dest is a file!");
			}
			if(FileUtil.isSub(src, dest)) {
				throw new IORuntimeException("Dest is a sub directory of src !");
			}

			final File subTarget = isCopyContentIfDir ? dest : FileUtil.mkdir(FileUtil.file(dest, src.getName()));
			internalCopyDirContent(src, subTarget);
		} else {// 复制文件
			dest = internalCopyFile(src, dest);
		}
		return dest;
	}

	//----------------------------------------------------------------------------------------- Private method start
	/**
	 * 拷贝目录内容,只用于内部,不做任何安全检查<br>
	 * 拷贝内容的意思为源目录下的所有文件和目录拷贝到另一个目录下,而不拷贝源目录本身
	 *
	 * @param src 源目录
	 * @param dest 目标目录
	 * @throws IORuntimeException IO异常

View on GitHub (pinned to 8870454b2a)

Solutions

  1. Choose a dest that is outside the src directory tree.
  2. Copy to a sibling or ancestor location first, then move if needed.
  3. Check FileUtil.isSub(src, dest) before copy() and reject early.

Example fix

// before
FileCopier.create(new File("/a"), new File("/a/b")).copy(); // throws: sub dir

// after: copy outside the tree, then relocate
File staging = new File("/tmp/staging_b");
FileCopier.create(new File("/a"), staging).copy();
// optionally move staging into /a after copy completes
Defensive patterns

Strategy: validation

Validate before calling

if (src.isDirectory() && FileUtil.isSub(src, dest)) {
    // dest is inside src: choose an outside target
    throw new IllegalArgumentException("dest must not be inside src");
}
return FileCopier.create(src, dest).copy();

Type guard

static boolean isSafeCopyTarget(File src, File dest) {
    return !(src != null && src.isDirectory() && FileUtil.isSub(src, dest));
}

Try / catch

try {
    return FileCopier.create(src, dest).copy();
} catch (IORuntimeException e) {
    if (e.getMessage().contains("sub directory of src")) {
        // dest is inside src: copy to an outside staging dir first
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling copy() on a FileCopier where dest is inside src — e.g. src=/a, dest=/a/b.

Common situations: Copying a folder into itself or one of its children; a backup-into-source mistake; dest computed as a subpath of src.

Related errors


AI-assisted analysis of chinabugotech/hutool@8870454b2a (2026-08-14). Data as JSON: /api/errors/c3480503f875a48a. Report an issue: GitHub.