Skip to content

Read an empty Locale string back as Locale.ROOT#3045

Open
vasiliy-mikhailov wants to merge 2 commits into
google:mainfrom
vasiliy-mikhailov:fix/locale-empty-string-root
Open

Read an empty Locale string back as Locale.ROOT#3045
vasiliy-mikhailov wants to merge 2 commits into
google:mainfrom
vasiliy-mikhailov:fix/locale-empty-string-root

Conversation

@vasiliy-mikhailov

@vasiliy-mikhailov vasiliy-mikhailov commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Gson serializes Locale.ROOT to an empty string (Locale.toString() returns ""), but deserializing "" back to a Locale threw a NullPointerException: the tokenizer produced no tokens, so language stayed null and was passed to new Locale(null).

Defaulting language to the empty string makes the empty form round-trip to Locale.ROOT. Added a regression test.

AI assistance disclosure

This contribution was produced with the help of an AI pipeline. The pipeline processed a large amount of source code to surface suspected bugs, reproduced a subset of them with failing unit tests and generated candidate fixes, and prepared pull requests from the ones that held up. Each PR was then reviewed and verified by a human before being opened: the fix and test were checked by hand and the test was confirmed to fail before the change and pass after.

Gson serializes Locale.ROOT to an empty string, but reading it back threw a
NullPointerException: with no tokens the language stayed null and was passed to
new Locale(null). Default the language to the empty string so the empty form
round-trips to Locale.ROOT.

@Marcono1234 Marcono1234 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

I have added a few suggestion comments, hopefully they are useful. The direct project members might have additional comments.

String locale = in.nextString();
StringTokenizer tokenizer = new StringTokenizer(locale, "_");
String language = null;
String language = "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems to work (new Locale("") creates something which acts like Locale.ROOT), but maybe it would be better to explicitly handle this case.
That is, keep = null here and then below add a if (language == null) { return Locale.ROOT; }?

What do you think?

Comment on lines 924 to 925

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to these changes, but maybe could omit the variant == null here?

- if (country == null && variant == null) {
+ if (country == null) {

If I understand the code above correctly, due to the hasMoreElements() checks country == null implies variant == null. The current (redundant?) variant == null check irritates null analysis (e.g. in IntelliJ IDEA), making it think for new Locale(language, country, variant) country can actually be null.

(Side note: IntelliJ did also identify the new Locale(language) as potentially causing a NullPointerException, which you proved in your PR here.)

This is only a suggestion though; feel free to ignore this and keep the code as it is.


@Test
public void testLocaleDeserializationRoot() {
// Gson serializes Locale.ROOT to "", so it must read "" back to Locale.ROOT.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For completeness it would probably be good to have also a serialization test which shows that Locale.ROOT is actually serialized as "\"\"".

Per review: default language to null and return Locale.ROOT explicitly for an
empty tag, drop the redundant variant == null check (country == null already
implies it), and add a test that Locale.ROOT serializes to an empty string.
@vasiliy-mikhailov

Copy link
Copy Markdown
Contributor Author

Thanks @Marcono1234! Adopted all three:

  1. Switched to the explicit if (language == null) { return Locale.ROOT; } guard — agreed it reads more clearly than relying on new Locale("").
  2. Dropped the redundant variant == null, so it's now just if (country == null). As you noted, the hasMoreElements() order means country == null already implies variant == null, and it keeps null analysis happy.
  3. Added testLocaleSerializationRoot asserting Locale.ROOT serializes to "", so the round-trip is covered from both sides.

(And credit to IntelliJ for flagging the new Locale(language) NPE.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants