chinabugotech/hutool · error · IllegalArgumentException

invalid hash: {hash}

Error message

invalid hash: {hash}

What it means

Thrown by Hashids.decode() after the round-trip integrity check fails: the decoded numbers are re-encoded and if the result does not match the input hash, the hash is considered corrupt or foreign. This guard ensures decode never silently returns garbage values from a tampered or mismatched hash. The hash was either not produced by this Hashids instance (different salt/alphabet) or was altered after generation.

Source

Thrown at hutool-core/src/main/java/cn/hutool/core/codec/Hashids.java:384

					}

					// shuffle the alphabet
					shuffle(currentAlphabet, decodeSalt);

					// prepend the decoded value
					final long n = translate(block.toString().toCharArray(), currentAlphabet);
					decoded = LongStream.concat(decoded, LongStream.of(n));

					// create a new block
					block = new StringBuilder(length);
				}
			}
		}

		// validate the hash
		final long[] decodedValue = decoded.toArray();
		if (!Objects.equals(hash, encode(decodedValue))) {
			throw new IllegalArgumentException("invalid hash: " + hash);
		}

		return decodedValue;
	}

	private StringBuilder translate(final long n, final char[] alphabet,
									final StringBuilder sb, final int start) {
		long input = n;
		do {
			// prepend the chosen char
			sb.insert(start, alphabet[(int) (input % alphabet.length)]);

			// trim the input
			input = input / alphabet.length;
		} while (input > 0);

		return sb;
	}

View on GitHub (pinned to 8870454b2a)

Solutions

  1. Verify the salt, alphabet, and minLength passed to the Hashids constructor are identical on both the encode and decode sides.
  2. Catch IllegalArgumentException and treat the input as invalid/untrusted — return a 404 or error response instead of crashing.
  3. If migrating salt or alphabet, re-encode all stored hashes before switching, or maintain a legacy Hashids instance for old hashes.
  4. Sanitize user-supplied hash strings before passing to decode — check length and character set against the configured alphabet.

Example fix

// before
Hashids hashids = new Hashids("mysalt".toCharArray(), DEFAULT_ALPHABET, -1);
long[] ids = hashids.decode(userInput); // throws on bad input

// after
Hashids hashids = new Hashids("mysalt".toCharArray(), DEFAULT_ALPHABET, -1);
long[] ids;
try {
    ids = hashids.decode(userInput);
} catch (IllegalArgumentException e) {
    // treat as not found
    ids = null;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate hash charset before decode
boolean valid = hash != null && hash.chars().allMatch(c -> {
    char ch = (char) c;
    return java.util.Arrays.binarySearch(Hashids.DEFAULT_ALPHABET, ch) >= 0 || "cfhistuCFHISTU".indexOf(ch) >= 0;
});

Try / catch

try {
    long[] ids = hashids.decode(hash);
} catch (IllegalArgumentException e) {
    // hash is invalid or was produced by a different configuration
    logger.warn("Invalid hash decode attempt: {}", hash);
    return null; // or throw a domain-specific exception
}

Prevention

When it happens

Trigger: Calling hashids.decode("someHash") where the hash was generated by a different Hashids instance using a different salt, alphabet, or minLength. Also triggered by manually editing a hash string, truncating it, or passing a hash from a different library implementation. Calling decodeToHex() with the same invalid input also propagates this error.

Common situations: Salt mismatch between encode and decode sides (e.g., salt loaded from different config files or environment values across services). Upgrading or changing the Hashids alphabet without re-encoding existing stored hashes. Passing a user-supplied ID from a URL parameter that was malformed or spoofed. Using a different Hashids library version that produces different output.

Related errors


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