HMCL-dev/HMCL · error · FileAlreadyExistsException

An unregistered instance directory already exists

Error message

An unregistered instance directory already exists

What it means

putManifest() validates new instance ids before accepting them: if the id is not in the base snapshot and not already created by this draft, it is treated as a brand-new instance and its directory must not exist on disk. If a directory is already present at the instance root, it throws FileAlreadyExistsException with the message 'An unregistered instance directory already exists' — HMCL will not silently adopt or overwrite an unregistered folder.

Solutions

  1. Remove or rename the existing unregistered directory at the instance root before calling put
  2. Pick a different, non-colliding instance id for the new instance
  3. If the existing folder is a valid instance you want to keep, load/import it through the repository instead of creating a new one with the same id
  4. Catch FileAlreadyExistsException, inspect the path in the message, and either clean it up or surface a 'name already in use' prompt to the user

Example fix

// before
try (Draft d = repo.openDraft()) {
    d.put(id, manifest); // FileAlreadyExistsException if dir exists
}
// after
Path root = repo.getLayout().getInstanceRoot(id);
if (Files.exists(root)) {
    throw new IllegalArgumentException("Instance name in use, pick another: " + id);
}
try (Draft d = repo.openDraft()) {
    d.put(id, manifest);
}
Defensive patterns

Strategy: validation

Validate before calling

Path root = layout.getInstanceRoot(id).toAbsolutePath().normalize();
if (Files.exists(root)) {
    throw new IllegalArgumentException("Directory already exists for id: " + id);
}

Try / catch

try {
    draft.put(id, manifest);
} catch (FileAlreadyExistsException e) {
    LOG.warning("Unregistered directory in the way: " + e.getFile());
    // ask user to clean up or choose another id
}

Prevention

When it happens

Trigger: Calling draft.put(...) (or putManifest via rename) with an id whose instance directory already exists on disk but which is not registered in the repository — the folder exists but has no known manifest.

Common situations: Creating an instance with the same name as a previously deleted (but not fully removed) instance; users placing raw version folders into the instances directory manually; a prior failed create that left the directory behind; case-insensitive filesystems making ids appear distinct but collide on disk.

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/ef253e5f0f61813c. Report an issue: GitHub.

Appendix: source

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

    /// Updates one manifest in the in-memory write set.
    ///
    /// @param manifest     the manifest to retain
    /// @param claimNewRoot whether a previously absent instance root should become draft-owned
    /// @throws IOException if a new instance root cannot be reserved
    private void putManifest(
            GameInstanceManifest manifest,
            boolean claimNewRoot) throws IOException {

        GameInstanceID id = manifest.id();
        if (claimNewRoot
                && !manifests.containsKey(id)
                && baseSnapshot.get(id) == null
                && !createdIds.contains(id)) {
            Path root = baseSnapshot.getLayout().getInstanceRoot(id)
                    .toAbsolutePath()
                    .normalize();
            if (!Files.notExists(root)) {
                throw new FileAlreadyExistsException(root.toString(), null,
                        "An unregistered instance directory already exists");
            }
            createdIds.add(id);
        }

        manifests.put(id, manifest);
        removedIds.remove(id);
        modifiedIds.add(id);
    }

    /// {@inheritDoc}
    @Override
    public DefaultGameRepositorySnapshot commit() throws IOException {
        checkOpen();
        repository.checkActiveDraft(this);
        state = GameRepositoryDraft.State.COMMITTING;

        List<RenameOperation> appliedRenames = new ArrayList<>();

View on GitHub (pinned to 24702dc5a0)