quarkusio/quarkus · error · IllegalArgumentException

Length must be 1, 2 or 6 but was:

Error message

Length must be 1, 2 or 6 but was: 

What it means

JsonEscaper packs escape-replacement metadata (two chars plus a length of 1, 2, or 6) into a single int via bit packing. Length 1 is a simple single-char replacement, 2 a two-char replacement like \n, and 6 a unicode escape like \u0001. Any other length would corrupt the packed int, so IllegalArgumentException is thrown. This is an internal invariant of table generation for JSON escaping.

Source

Thrown at independent-projects/qute/core/src/main/java/io/quarkus/qute/JsonEscaper.java:33

    /**
     * Packs the replacement data into a single int.<br>
     * The replacement data is packed as follows:<br>
     * write an ASCII art of the int:<br>
     * The visual order chosen reflect what Integer::toHexString would print since Java ints are stored big-endian.<br>
     *
     * <pre>
     *         |----------|-----------|-------------|------------|
     *  bits   |   24-31  |   16-23   |    8-15     |    0-7     |
     *  field  |  length  |  padding  |   2nd char  |  1st char  |
     *  values |  {1,2,6} |    [0]    |   [0-255]   |   [0-255]  |
     *         |----------|-----------|-------------|------------|
     * </pre>
     *
     */
    private static int packReplacementData(int first, int second, int length) {
        if (length != 1 && length != 2 && length != 6) {
            throw new IllegalArgumentException("Length must be 1, 2 or 6 but was: " + length);
        }
        if (first < 0 || first > 255) {
            throw new IllegalArgumentException("First char must be in range [0, 255] but was: " + first);
        }
        if (second < 0 || second > 255) {
            throw new IllegalArgumentException("Second char must be in range [0, 255] but was: " + second);
        }
        return (first | (second << SECOND_CHAR_OFFSET)) | (length << LENGTH_BITS_OFFSET);
    }

    private static int replacementLength(int replacementData) {
        // length isn't bigger than 127, which means preserving sign (which is faster) won't affect the shift
        return replacementData >> LENGTH_BITS_OFFSET;
    }

    private static char secondChar(int replacementData) {
        // since past the second char we have padding === 0 we can just cast to char
        return (char) (replacementData >> SECOND_CHAR_OFFSET);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Use only replacement strings whose escaped length is 1 (e.g. one char), 2 (e.g. \\n), or 6 (e.g. \\uXXXX)
  2. Recompute the length argument from the replacement string: "\\n".length()==2, "\\u0041".length()==6
  3. If you need a different replacement length, extend the bit-packing scheme (LENGTH_BITS_OFFSET) rather than passing an out-of-range value

Example fix

// before
packReplacementData('a', 'x', 3); // throws
// after
packReplacementData('\\', 'n', 2); // encodes the two-char replacement \n
Defensive patterns

Strategy: validation

Validate before calling

if (length != 1 && length != 2 && length != 6) {
    throw new IllegalArgumentException("length must be 1, 2 or 6, got " + length);
}

Type guard

boolean isValidReplacementLength(int len) {
    return len == 1 || len == 2 || len == 6;
}

Try / catch

try {
    int packed = packReplacementData(first, second, length);
} catch (IllegalArgumentException e) {
    log.error("Bad replacement metadata: " + e.getMessage());
    throw e;
}

Prevention

When it happens

Trigger: Calling the private packReplacementData with a length other than 1, 2, or 6 — only possible when modifying JsonEscaper's replacement tables (e.g. adding a new escape entry with a wrong replacement length) or via reflection.

Common situations: Developers contributing new escaped characters to Qute's JSON escaper and mis-specifying the replacement string length; not reachable from user templates.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/91a7fb543050c111. Report an issue: GitHub.