hibernate/hibernate-orm · error · IllegalArgumentException
Invalid extension: %s
Error message
Invalid extension: %s
What it means
handleExtension() in LocaleJavaType validates the extension segment of a legacy locale value: it must be at least 3 characters long and its second character must be '-' (shape 'k-...' like 'u-ca-gregory' or 'x-privatedata'). It is reached for the segment after a '#script' part, for a '#'-segment that is not a valid 4-letter script, or after an extension in state EXTENSION. Anything malformed throws IllegalArgumentException("Invalid extension: ...").
Source
Thrown at hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocaleJavaType.java:167
case EXTENSION -> {
handleExtension( chars, start, length, builder );
yield END;
}
case END -> throw new IllegalStateException( "Unexpected continuation of locale value after extension: " + new String( chars, start, length ) );
};
}
private boolean isScript(char[] chars, int start, int length) {
return length == 4
&& isLetter( chars[start] )
&& isLetter( chars[start + 1] )
&& isLetter( chars[start + 2] )
&& isLetter( chars[start + 3] );
}
private void handleExtension(char[] chars, int start, int length, Locale.Builder builder) {
if ( length < 3 || chars[start + 1] != '-' ) {
throw new IllegalArgumentException( "Invalid extension: " + new String( chars, start, length ) );
}
if ( toLowerCase( chars[start] ) == 'u' ) {
// After a Unicode extension, there could come a private use extension which we need to detect
int unicodeStart = start + 2;
int unicodeLength = length - 2;
final int end = start + length;
for ( int i = start + 2; i < end; i++ ) {
if ( chars[i] == '-' && i + 3 < end && chars[i + 1] == 'x' && chars[i + 2] == '-' ) {
builder.setExtension( 'x', new String( chars, i + 3, end - i - 3 ) );
unicodeLength = i - unicodeStart;
break;
}
}
builder.setExtension( chars[start], new String( chars, unicodeStart, unicodeLength ) );
}
else {
builder.setExtension( chars[start], new String( chars, start + 2, length - 2 ) );
}View on GitHub (pinned to fad1729dce)
Solutions
- Store BCP 47 language tags with '-' separators; the parser then uses setLanguageTag and full extension syntax works
- For legacy form, use exactly one underscore between segments and dashes inside the extension ('en_US_var#Latn_u-ca-gregory')
- Normalize inbound strings with Locale.Builder before persisting
- Fix seed/import data that contains underscore-separated extension keywords
Example fix
// before String stored = "en_US_var#Latn_u_ca_gregory"; // Invalid extension: u // after String stored = "en-US-u-ca-gregory"; // BCP 47 // legacy alternative: "en_US_var#Latn_u-ca-gregory"
Defensive patterns
Strategy: validation
Validate before calling
static boolean looksLikeLegacyExtension(String seg) {
return seg.length() >= 3 && seg.charAt(1) == '-';
}
// validate '#'-segments before persisting underscore-format locales
static boolean validSegment(String seg) {
return seg.length() == 4 && seg.chars().allMatch(Character::isLetter) // script
|| looksLikeLegacyExtension(seg); // 'u-ca-...'
} Try / catch
catch (IllegalArgumentException e) {
// message starts with "Invalid extension: "
throw new IllegalArgumentException("Extension must look like 'k-value' (dash at index 1): " + value, e);
} Prevention
- Use '-' inside extensions and '_' between legacy segments
- Store BCP 47 tags to avoid the hand-rolled grammar entirely
- Test locale strings through LocaleJavaType.fromString in unit tests for seed data
When it happens
Trigger: '#'-segments that are not 4 letters get treated as extensions and fail, e.g. 'en_US_var#ca-gregory' (second char 'a', not '-'); extension segments shorter than 3 chars, e.g. '..._u'; BCP 47 extensions written with underscores like 'en_US_var#Latn_u_ca_gregory' (inner underscores split the segment so chars[start+1] is not '-')
Common situations: Converting BCP 47 tags to underscore format by naive replace('-','_'); hand-built locale strings in seed data; copy/paste from documentation examples with wrong separators.
Related errors
- Invalid script: %s
- Unexpected continuation of locale value after extension: %s
- Decomposition not supported for %s
- Unexpected PostgreSQL lock_timeout format: {}
- Named query exists, but did not specify a resultClass
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/757d5e37db9269bd.
Report an issue: GitHub.