HMCL-dev/HMCL · error · InvalidSkinException

Invalid skin size

Error message

Invalid skin size

What it means

Thrown by AccountListItem.uploadSkin when the decoded skin image does not have the dimensions Minecraft skins require: width must be 64 and height 32 (legacy) or 64 (modern). The file decodes fine but is simply the wrong size.

Solutions

  1. Resize/re-export the skin to exactly 64x64 (or legacy 64x32) in an image editor or skin editor
  2. Use an HD-skin-capable upload path/provider if a 128x128 skin is intentional
  3. Pick the correct skin file instead of a screenshot/avatar
  4. Validate dimensions client-side before calling uploadSkin to give a friendlier error

Example fix

// before
account.uploadSkin(file); // file is 128x128 -> InvalidSkinException
// after
BufferedImage img = ImageIO.read(file.toFile());
if (img.getWidth() != 64 || (img.getHeight() != 32 && img.getHeight() != 64)) {
    img = resizeToSkin(img, 64, 64); // or reject
}
Defensive patterns

Strategy: validation

Validate before calling

BufferedImage img = ImageIO.read(file.toFile());
boolean valid = img != null && img.getWidth() == 64 && (img.getHeight() == 32 || img.getHeight() == 64);
if (!valid) throw new IllegalArgumentException("Skin must be 64x32 or 64x64");

Type guard

static boolean hasSkinDimensions(java.awt.image.BufferedImage img) {
    return img.getWidth() == 64 && (img.getHeight() == 32 || img.getHeight() == 64);
}

Try / catch

try {
    uploadSkin(file);
} catch (InvalidSkinException e) {
    if ("Invalid skin size".equals(e.getMessage())) {
        Dialogs.error("Skins must be 64x32 or 64x64 pixels.");
    }
}

Prevention

When it happens

Trigger: Uploading a skin PNG whose width != 64 or height not in {32, 64} — e.g. HD skins (128x128), cropped screenshots, or avatar images.

Common situations: Users selecting high-resolution skin packs (128x128) that vanilla accounts don't accept via this path, or arbitrary images mistaken for skins.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java:170

        chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("account.skin.file"), "*.png"));
        Path selectedFile = Controllers.showOpenDialog(chooser);
        if (selectedFile == null) {
            return null;
        }

        return refreshAsync()
                .thenRunAsync(() -> {
                    Image skinImg;
                    try (var input = Files.newInputStream(selectedFile)) {
                        skinImg = new Image(input);
                    } catch (IOException e) {
                        throw new InvalidSkinException("Failed to read skin image", e);
                    }
                    if (skinImg.isError()) {
                        throw new InvalidSkinException("Failed to read skin image", skinImg.getException());
                    }
                    if (skinImg.getWidth() != 64 || (skinImg.getHeight() != 32 && skinImg.getHeight() != 64)) {
                        throw new InvalidSkinException("Invalid skin size");
                    }
                    NormalizedSkin skin = new NormalizedSkin(skinImg);
                    String model = skin.isSlim() ? "slim" : "";
                    LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]");
                    account.uploadSkin(skin.isSlim(), selectedFile);
                })
                .thenComposeAsync(refreshAsync())
                .whenComplete(Schedulers.javafx(), e -> {
                    if (e != null) {
                        Controllers.dialog(Accounts.localizeErrorMessage(e), i18n("account.skin.upload.failed"), MessageType.ERROR);
                    }
                });
    }

    public void remove() {
        if (!Accounts.canRemoveAccount(account)) {
            Controllers.confirmBackupAndOverwrite(i18n("account.storage.read_only"), () -> {
                Accounts.forceOverwriteAccountFiles(account);

View on GitHub (pinned to 24702dc5a0)