HMCL-dev/HMCL · error · IllegalArgumentException

Illegal pattern: Missing Key

Error message

Illegal pattern: ${value} Missing Key: ${key}

What it means

Thrown as IllegalArgumentException from replaceTokens when a '{Key}' placeholder refers to a variable not present in the tokens (vars) map. The processor profile references data (e.g. a substitution variable) that this install task never populated, so token substitution cannot proceed.

Solutions

  1. Use the official installer for the target NeoForge/Forge version so placeholder names match what the launcher supplies
  2. Compare the missing key from the error message against vars populated in NeoForgeOldInstallTask.execute (SIDE, MINECRAFT_JAR, MINECRAFT_VERSION, ROOT, INSTALLER, LIBRARY_DIR, plus profile data)
  3. Update HMCL — newer profiles may need newer launcher support
  4. Report the version to HMCL if the official profile still references an unsupported key

Example fix

// before (profile references undefined key)
{CUSTOM_PATCH}
// after (defined key)
{MINECRAFT_JAR}
Defensive patterns

Strategy: validation

Validate before calling

// verify all placeholders resolve before running processors
java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\{([^}]+)\\}").matcher(arg);
while (m.find()) if (!vars.containsKey(m.group(1))) throw new IllegalStateException("Missing var: " + m.group(1));

Try / catch

try { task.run(); } catch (IllegalArgumentException e) { if (e.getMessage() != null && e.getMessage().startsWith("Illegal pattern:")) reportMissingVar(e.getMessage()); else throw e; }

Prevention

When it happens

Trigger: A plain (non fully-bracketed/quoted) processor arg contains {Key} where tokens.containsKey(key) is false — the key is neither in profile.getData()-derived vars nor the injected SIDE/MINECRAFT_JAR/MINECRAFT_VERSION/ROOT/INSTALLER/LIBRARY_DIR entries.

Common situations: Unsupported or modified install_profile.json introducing new placeholders; launcher not supplying a variable expected by a newer Forge/NeoForge processor format; typo in the variable name in a hand-built profile.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java:247

                            throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value);
                        key.append(value.charAt(++y));
                    } else {
                        if (c == '{' && d == '}') {
                            x = y;
                            break;
                        }
                        if (c == '\'' && d == '\'') {
                            x = y;
                            break;
                        }
                        key.append(d);
                    }
                }
                if (c == '\'') {
                    buf.append(key);
                } else {
                    if (!tokens.containsKey(key.toString()))
                        throw new IllegalArgumentException("Illegal pattern: " + value + " Missing Key: " + key);
                    buf.append(tokens.get(key.toString()));
                }
            } else {
                buf.append(c);
            }
        }
        return buf.toString();
    }

    private <E extends Exception> String parseLiteral(String literal, Map<String, String> var, ExceptionalFunction<String, String, E> plainConverter) throws E {
        if (StringUtils.isSurrounded(literal, "{", "}"))
            return var.get(StringUtils.removeSurrounding(literal, "{", "}"));
        else if (StringUtils.isSurrounded(literal, "'", "'"))
            return StringUtils.removeSurrounding(literal, "'");
        else if (StringUtils.isSurrounded(literal, "[", "]"))
            return gameRepository.getLayout().getArtifactFile(Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
        else
            return plainConverter.apply(replaceTokens(var, literal));

View on GitHub (pinned to 24702dc5a0)