hibernate/hibernate-orm · error · IllegalArgumentException
Invalid script: %s
Error message
Invalid script: %s
What it means
LocaleJavaType.fromString() parses legacy underscore-separated locale values with a small state machine: language_region_variant#Script_extension. After a variant segment was read (state SCRIPT), the next underscore-separated segment must start with '#' and be at least 5 characters ('#Latn'); the 4 characters after '#' must be letters to count as a script, otherwise the code falls through to extension handling. A segment that is shorter than 5 chars or does not start with '#' throws IllegalArgumentException("Invalid script: ...").
Source
Thrown at hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocaleJavaType.java:138
case VARIANT -> {
if ( chars[start] == '#' ) {
if ( isScript( chars, start + 1, length - 1 ) ) {
builder.setScript( new String( chars, start + 1, length - 1 ) );
yield EXTENSION;
}
else {
handleExtension( chars, start + 1, length - 1, builder );
yield END;
}
}
else {
builder.setVariant( new String( chars, start, length ) );
yield SCRIPT;
}
}
case SCRIPT -> {
if ( length < 5 || chars[start] != '#' ) {
throw new IllegalArgumentException( "Invalid script: " + new String( chars, start, length ) );
}
if ( isScript( chars, start + 1, length - 1 ) ) {
builder.setScript( new String( chars, start + 1, length - 1 ) );
yield EXTENSION;
}
else {
handleExtension( chars, start + 1, length - 1, builder );
yield END;
}
}
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 ) );
};
}
View on GitHub (pinned to fad1729dce)
Solutions
- Store BCP 47 language tags instead: the parser detects '-' during the language segment and delegates to Locale.Builder.setLanguageTag, which is far more tolerant
- Keep legacy values in the exact grammar language_region_variant#Script (script exactly 4 letters, '#' prefix)
- Validate and normalize locale strings before persisting: Locale.forLanguageTag(s) then toLanguageTag()
- Clean existing rows that carry trailing underscore segments or malformed '#script' parts
Example fix
// before String locale = "en_US_default_variant"; // stored in Locale column -> Invalid script: default // after String locale = "en-US"; // BCP 47 tag, parsed via setLanguageTag // or legacy form: "en_US_default#Latn"
Defensive patterns
Strategy: validation
Validate before calling
static boolean isValidHibernateLocale(String s) {
if (s == null || s.isEmpty()) return true; // empty -> Locale.ROOT
try {
org.hibernate.type.descriptor.java.LocaleJavaType.INSTANCE.fromString(s);
return true;
} catch (RuntimeException e) { return false; }
}
// at the write boundary:
if (!isValidHibernateLocale(input)) reject(input); Type guard
static java.util.Locale tryLocale(String s) {
try { return java.util.Locale.forLanguageTag(s.replace('_','-')); }
catch (Exception e) { return null; }
} Try / catch
catch (IllegalArgumentException e) {
// message starts with "Invalid script: "
throw new IllegalArgumentException("Locale must be 'lang_REGION_variant#Script_ext' or a BCP 47 tag: " + value, e);
} Prevention
- Store BCP 47 language tags ('en-US'); Hibernate routes them through setLanguageTag
- Keep legacy values in grammar order language_region_variant#Script (script exactly 4 letters after '#')
- Normalize with Locale.forLanguageTag(...).toLanguageTag() before persisting user input
When it happens
Trigger: A stored locale value with a fourth underscore segment lacking '#', e.g. 'en_US_some_variant_x'; a '#'-segment shorter than 5 characters, e.g. 'en_US_var#Lat'; malformed script casing/length like 'en_US_var#Latin' (5 letters, so treated as extension and rejected there)
Common situations: User-profile locale strings ingested from external systems into a Locale-mapped column; teams writing BCP 47 tags ('zh-Hans-CN') but replacing '-' with '_' before storing; concatenating extra keys to locale codes ('en_US_default').
Related errors
- Invalid extension: %s
- Unexpected continuation of locale value after extension: %s
- Unexpected PostgreSQL lock_timeout format: {}
- Named query exists, but did not specify a resultClass
- The return type for a multivalued result set mapping should
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/6d4ce56ca3182947.
Report an issue: GitHub.