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

  1. Store BCP 47 language tags with '-' separators; the parser then uses setLanguageTag and full extension syntax works
  2. For legacy form, use exactly one underscore between segments and dashes inside the extension ('en_US_var#Latn_u-ca-gregory')
  3. Normalize inbound strings with Locale.Builder before persisting
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/757d5e37db9269bd. Report an issue: GitHub.