{"record":{"id":"29fabd08e3c7c4c0","repo":"apache/flink","slug":"comparator-classname-specifies-an-invalid-length","errorCode":null,"errorMessage":"Comparator {className} specifies an invalid length for the normalized key: {len}","messagePattern":"Comparator (.+?) specifies an invalid length for the normalized key: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"flink-core/src/main/java/org/apache/flink/api/java/typeutils/runtime/TupleComparatorBase.java","lineNumber":93,"sourceCode":"        for (int i = 0; i < this.keyPositions.length; i++) {\n            TypeComparator<?> k = this.comparators[i];\n\n            // as long as the leading keys support normalized keys, we can build up the composite\n            // key\n            if (k.supportsNormalizedKey()) {\n                if (i == 0) {\n                    // the first comparator decides whether we need to invert the key direction\n                    inverted = k.invertNormalizedKey();\n                } else if (k.invertNormalizedKey() != inverted) {\n                    // if a successor does not agree on the inversion direction, it cannot be part\n                    // of the normalized key\n                    break;\n                }\n\n                nKeys++;\n                final int len = k.getNormalizeKeyLen();\n                if (len < 0) {\n                    throw new RuntimeException(\n                            \"Comparator \"\n                                    + k.getClass().getName()\n                                    + \" specifies an invalid length for the normalized key: \"\n                                    + len);\n                }\n                this.normalizedKeyLengths[i] = len;\n                nKeyLen += len;\n\n                if (nKeyLen < 0) {\n                    // overflow, which means we are out of budget for normalized key space anyways\n                    nKeyLen = Integer.MAX_VALUE;\n                    break;\n                }\n            } else {\n                break;\n            }\n        }\n        this.numLeadingNormalizableKeys = nKeys;","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/apache/flink/blob/2f3c205e9266cb30240eb7f4fdab15cad629a70f/flink-core/src/main/java/org/apache/flink/api/java/typeutils/runtime/TupleComparatorBase.java#L75-L111","documentation":"TupleComparatorBase's constructor precomputes normalized-key metadata by asking each field comparator for getNormalizeKeyLen(). A length < 0 is invalid (negative means 'infinite' only via supportsNormalizedKeyOnReference/ other flags, and a negative len here breaks key-space math), so it throws RuntimeException naming the comparator class and the bad length. This is a contract violation by a custom field-level TypeComparator.","triggerScenarios":"Supplying a custom TypeComparator as a field comparator whose getNormalizeKeyLen() returns a negative value while it still participates in the normalized key computation; TupleComparator/TupleComparatorBase constructor then throws during comparator creation (client- or setup-time).","commonSituations":"User implements a custom comparator and returns -1 from getNormalizeKeyLen() unconditionally (copying a template) while supportsNormalizedKey() returns true. Flink version change altered expectations around normalize-key lengths. Third-party comparator implementations that never were exercised with tuple composition.","solutions":["Fix the custom comparator: getNormalizeKeyLen() must return >= 0 whenever it advertises a normalizable key; if it cannot produce a normalized key, override supportsNormalizedKey() to return false instead of returning a negative length.","Add a unit test asserting getNormalizeKeyLen() >= 0 for the custom comparator under all configurations.","As a workaround while fixing, use a built-in comparator for that field, or wrap the custom one so normalized-key support is disabled."],"exampleFix":"// before\npublic class MyComparator extends TypeComparator<Long> {\n    @Override public int getNormalizeKeyLen() { return -1; }\n    @Override public boolean supportsNormalizedKey() { return true; }\n}\n\n// after\npublic class MyComparator extends TypeComparator<Long> {\n    @Override public boolean supportsNormalizedKey() { return false; } // no normalized key\n    // or: @Override public int getNormalizeKeyLen() { return 8; } // Long.SIZE / Byte.SIZE\n}","handlingStrategy":"validation","validationCode":"public static void checkFieldComparators(TypeComparator<?>[] fieldComparators) {\n    for (TypeComparator<?> k : fieldComparators) {\n        if (k.supportsNormalizedKey() && k.getNormalizeKeyLen() < 0) {\n            throw new IllegalStateException(k.getClass().getName()\n                + \" advertises a normalized key but returns length \" + k.getNormalizeKeyLen());\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    new TupleComparator<>(keyPositions, fieldComparators, fieldSerializers);\n} catch (RuntimeException e) {\n    // message names the offending comparator class and length; fix getNormalizeKeyLen()\n}","preventionTips":["Custom comparators: getNormalizeKeyLen() >= 0 or supportsNormalizedKey() == false.","Unit-test the normalize-key contract of every custom comparator.","Prefer built-in field comparators where possible."],"tags":["comparator","normalized-key","custom-comparator","contract-violation"],"backgroundTag":null,"analyzedSha":"2f3c205e9266cb30240eb7f4fdab15cad629a70f","analyzedAt":"2026-08-14T08:48:24.518Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}