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

  1. Store BCP 47 language tags instead: the parser detects '-' during the language segment and delegates to Locale.Builder.setLanguageTag, which is far more tolerant
  2. Keep legacy values in the exact grammar language_region_variant#Script (script exactly 4 letters, '#' prefix)
  3. Validate and normalize locale strings before persisting: Locale.forLanguageTag(s) then toLanguageTag()
  4. 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

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


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