HMCL-dev/HMCL · error · FileAlreadyExistsException

${targetRoot}

Error message

${targetRoot}

What it means

During rename, the draft computes the target instance directory via the layout (getInstanceRoot(to)) and refuses to overwrite anything on disk: if a directory already exists at targetRoot, it throws FileAlreadyExistsException with that path (message '${targetRoot}'). This protects an existing instance's files from being clobbered by a rename.

Solutions

  1. Delete or move the existing directory at targetRoot before renaming, after confirming it holds no needed files
  2. Choose a different target instance id that does not collide with an existing folder
  3. Catch FileAlreadyExistsException and surface the path so the user can clean it up manually
  4. Clean up orphaned instance directories periodically so renames do not hit leftover folders

Example fix

// before
draft.rename(from, GameInstanceID.of("1.8.9")); // may throw FileAlreadyExistsException
// after
Path targetRoot = repo.getLayout().getInstanceRoot(newId);
if (Files.exists(targetRoot)) {
    Files.move(targetRoot, targetRoot.resolveSibling(targetRoot.getFileName() + ".bak"));
}
draft.rename(from, newId);
Defensive patterns

Strategy: try-catch

Validate before calling

Path targetRoot = layout.getInstanceRoot(to);
if (Files.exists(targetRoot)) {
    throw new IllegalArgumentException("Target dir exists: " + targetRoot);
}

Try / catch

try {
    draft.rename(from, to);
} catch (FileAlreadyExistsException e) {
    LOG.warning("Rename blocked, target directory exists: " + e.getFile());
    // prompt user to clean up or pick another name
}

Prevention

When it happens

Trigger: Renaming an instance to an id whose directory already exists under the repository root — e.g. the target id was previously used and left orphaned files on disk, even though it is not registered in the manifests map (which is why the earlier containsKey check passed).

Common situations: A previously deleted instance whose folder removal failed or was done outside HMCL; users manually creating folders in .minecraft/versions (or the repo root) with instance names; renaming to a name that collides with an unregistered leftover directory.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/9129e86a6a244fc1. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepositoryDraft.java:175

    /// {@inheritDoc}
    @Override
    public void rename(GameInstanceID from, GameInstanceID to) throws IOException {
        checkOpen();
        @Nullable GameInstanceManifest source = manifests.get(from);
        if (source == null) {
            throw new NoSuchGameInstanceException(from);
        }
        if (createdIds.contains(from)) {
            throw new IllegalStateException("Cannot rename an instance created by the same draft");
        }
        if (manifests.containsKey(to)) {
            throw new IllegalArgumentException("Target instance already exists: " + to);
        }

        Path targetRoot = baseSnapshot.getLayout().getInstanceRoot(to);
        if (Files.exists(targetRoot)) {
            throw new FileAlreadyExistsException(targetRoot.toString());
        }

        GameInstanceManifest renamedManifest = source;
        if (from.equals(renamedManifest.jar())) {
            renamedManifest = renamedManifest.withJar(null);
        }
        renamedManifest = renamedManifest.withId(to);

        manifests.remove(from);
        modifiedIds.remove(from);
        removedIds.remove(from);
        putManifest(renamedManifest, false);

        @Nullable Path primaryJarSource = primaryJarSources.remove(from);
        if (primaryJarSource != null) {
            primaryJarSources.put(to, primaryJarSource);
        }

View on GitHub (pinned to 24702dc5a0)