Localization ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-16

Java Locale Class: What a Locale Is and How to Create One

The string "en_US" looks like a valid locale code, but Locale.forLanguageTag("en_US") returns an empty locale: its toString() is "" and getDisplayLanguage() is an empty string too. No exception is thrown. ResourceBundle quietly loads the base resource file without a suffix, and nobody notices that the English texts never showed up. This lesson explains why that happens and how to create a Locale correctly.

A locale is a set of regional settings: a language, a country (region) and, when needed, a script and a variant. In Java a locale is represented by the java.util.Locale class. It lets your code respect regional conventions for alphabets, symbols, numbers and dates. When the JVM starts, it takes the default locale from the operating system settings, but you can change it or pass a specific locale to a particular method.

A Locale does not format anything by itself. It is a label for a language and region that you pass to NumberFormat, DateTimeFormatter, ResourceBundle and other classes. Why an application needs this at all is covered in the lesson on internationalization and localization in Java.

How to create a Locale: 5 ways

In modern Java you create a locale with the factory method Locale.of("de", "DE") (Java 19+) or from a BCP 47 language tag with Locale.forLanguageTag("de-DE"). For a handful of countries there are ready-made constants such as Locale.US. The new Locale(...) constructor still compiles, but it has been deprecated since Java 19.

Way Example Since What happens with invalid input
Constant Locale.FRANCE Java 1.1 (Locale.ROOT: Java 6) You can't get it wrong, but there are few constants
Locale.of(language, country) Locale.of("de", "DE") Java 19 Arguments are not validated: Locale.of("de_DE") gives the language "de_de" with no country
Locale.forLanguageTag(tag) Locale.forLanguageTag("de-DE") Java 7 An ill-formed subtag and everything after it is silently dropped: "en_US" → an empty locale
Locale.Builder new Locale.Builder().setLanguage("de").setRegion("DE").build() Java 7 Throws IllformedLocaleException
Constructor (deprecated) new Locale("de", "DE") Deprecated since Java 19 Does not validate arguments, just like Locale.of

All three modern ways produce the same locale:

import java.util.Locale;

public class CreateLocale {
    public static void main(String[] args) {
        Locale a = Locale.of("de", "DE");
        Locale b = Locale.forLanguageTag("de-DE");
        Locale c = new Locale.Builder().setLanguage("de").setRegion("DE").build();

        System.out.println(a + " " + b + " " + c);
        System.out.println(a.equals(b) && b.equals(c));
    }
}
// Output:
// de_DE de_DE de_DE
// true

The language code is written in lowercase (ISO 639: en, de, fr), the country code in uppercase (ISO 3166: US, DE, FR). If your project runs on Java 8–18, where Locale.of does not exist yet, use Locale.forLanguageTag: it has been available since Java 7 and is not deprecated in newer versions.

Locale.US, Locale.FRANCE and other constants

The Locale class has ready-made constants for a few countries (language + country) and languages (language only). There are no constants for most countries, for example Brazil or Spain. Create those locales with Locale.of("pt", "BR") or Locale.forLanguageTag("pt-BR").

Constant Value (toString) What it represents
Locale.US en_US English, United States
Locale.UK en_GB English, United Kingdom
Locale.CANADA / Locale.CANADA_FRENCH en_CA / fr_CA Canada: English / French
Locale.FRANCE, Locale.GERMANY, Locale.ITALY fr_FR, de_DE, it_IT France, Germany, Italy
Locale.JAPAN, Locale.KOREA, Locale.CHINA (= PRC), Locale.TAIWAN ja_JP, ko_KR, zh_CN, zh_TW Japan, Korea, China, Taiwan
Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.JAPANESE, Locale.KOREAN, Locale.CHINESE en, fr, de, it, ja, ko, zh Language only, no country
Locale.ROOT "" The neutral locale, see the next section

The difference between Locale.US and Locale.ENGLISH shows up, for example, when formatting currency: en_US has a country and therefore a currency (the dollar), while en has no country.

Locale.UK is not Ukraine

Locale.UK stands for the United Kingdom, that is en_GB. Ukrainian has the language code uk and Ukraine has the country code UA, so a Ukrainian locale is created as Locale.of("uk", "UA"). The class has no constant for it.

Locale.ROOT: a locale with no language or country

Locale.ROOT is the constant for the neutral locale whose language and country are both the empty string "". It is the base of all other locales and is used wherever a result must not depend on language or region: keys, identifiers, file and protocol formats.

System.out.println("[" + Locale.ROOT + "]");          // Output: []
System.out.println(Locale.ROOT.toLanguageTag());      // Output: und
System.out.println("[" + Locale.ROOT.getDisplayName() + "]"); // Output: []
System.out.println(Locale.ROOT.equals(Locale.of(""))); // Output: true

und is the BCP 47 tag for an "undetermined" language. Locale.ROOT is used for:

  • Locale-independent operations: toUpperCase(Locale.ROOT), String.format(Locale.ROOT, ...). Without an explicit locale these methods use the default locale, so the result on a server can differ from the result on the developer's machine. Examples of such bugs, including the Turkish letter i, are in the lesson on internationalization.
  • Base resources: a text.properties file without a suffix corresponds to Locale.ROOT. It is the last stop in the ResourceBundle lookup: the bundle uses it when neither the requested locale nor the default locale has its own file.

The default locale: getDefault and setDefault

Locale.getDefault() returns the current locale of the JVM. You can change it with Locale.setDefault(Locale) or with the startup options -Duser.language and -Duser.country. The default locale is determined once, when the JVM starts, from the operating system settings.

Locale current = Locale.getDefault();   // for example, en_US on an English-language Windows
Locale.setDefault(Locale.CANADA);       // now en_CA for the whole JVM

You can also set the locale at startup, without changing the code:

java -Duser.language=de -Duser.country=DE Main

Since Java 7 the default locale has two categories (Locale.Category). DISPLAY controls the language of names (getDisplayCountry() and so on), and FORMAT controls the format of numbers, dates and currencies. setDefault(Locale) changes both categories at once, while setDefault(Category, Locale) changes only one and does not touch the general getDefault():

import java.util.Locale;

public class DefaultLocaleCategories {
    public static void main(String[] args) {
        Locale start = Locale.getDefault();
        Locale.setDefault(Locale.Category.FORMAT, Locale.GERMANY);
        Locale.setDefault(Locale.Category.DISPLAY, Locale.ENGLISH);

        System.out.println(Locale.getDefault().equals(start)); // the general locale did not change
        System.out.println(String.format("%,.2f", 1234.5));    // the format comes from FORMAT
        System.out.println(Locale.FRANCE.getDisplayCountry()); // the name comes from DISPLAY
    }
}
// Output:
// true
// 1.234,50
// France

Calling Locale.setDefault(null) throws a NullPointerException.

setDefault affects the whole JVM

Locale.setDefault changes the locale for the running JVM instance, not for a thread or a user. On a server that handles users with different languages at the same time, don't change the default locale. Pass the request locale to formatters explicitly (in Spring it is kept by LocaleContextHolder).

Locale methods and an example

The methods of Locale fall into two groups. Codes (getLanguage(), getCountry()) return what is stored in the object. Names (getDisplayLanguage(), getDisplayCountry()) are translated into the language of the default DISPLAY locale, or into the language of the locale you pass as an argument.

Method What it returns For Locale.of("de", "DE")
getLanguage() ISO 639 language code de
getCountry() ISO 3166 country code or "" DE
getDisplayLanguage(Locale) Language name in the language of the argument Deutsch (argument de), German (argument Locale.ENGLISH)
getDisplayCountry(Locale) Country name in the language of the argument Deutschland / Germany
getDisplayName(Locale) Language and country in one string Deutsch (Deutschland) / German (Germany)
toString() Code with an underscore de_DE
toLanguageTag() BCP 47 tag with a hyphen de-DE
getISO3Language() / getISO3Country() Three-letter codes deu / DEU

Here is a Java Locale example. The default locale is replaced with Locale.CANADA, so all names are printed in English:

import java.util.Locale;

public class LocaleExample1 {
    public static void main(String[] args) {
        Locale fr = Locale.FRANCE;
        Locale us = Locale.US;
        Locale br = Locale.of("pt", "BR");

        Locale.setDefault(Locale.CANADA);
        Locale current = Locale.getDefault();
        getLocaleInfo(current);

        getLocaleInfo(fr);
        getLocaleInfo(us);
        getLocaleInfo(br);
    }

    private static void getLocaleInfo(Locale locale) {
        System.out.println("Country code: " + locale.getCountry());
        System.out.println("Country name: " + locale.getDisplayCountry());
        System.out.println("Language code: " + locale.getLanguage());
        System.out.println("Language name: "
                + locale.getDisplayLanguage());
        System.out.println();
    }
}

Output (Java 11+):

Country code: CA
Country name: Canada
Language code: en
Language name: English

Country code: FR
Country name: France
Language code: fr
Language name: French

Country code: US
Country name: United States
Language code: en
Language name: English

Country code: BR
Country name: Brazil
Language code: pt
Language name: Portuguese

To get names in German regardless of the default locale, pass the locale as an argument:

Locale de = Locale.of("de", "DE");

System.out.println(de.getDisplayName(de));                // Output: Deutsch (Deutschland)
System.out.println(Locale.FRANCE.getDisplayCountry(de));  // Output: Frankreich
System.out.println(Locale.FRANCE.getDisplayLanguage(de)); // Output: Französisch
System.out.println(de.toLanguageTag());                   // Output: de-DE
System.out.println(de.getISO3Country());                  // Output: DEU

Locale mistakes that fail silently

Most Locale mistakes do not throw an exception: a wrong locale is created silently, and the problem shows up later, when a text or a format turns out to be wrong. The main rule: toString() gives en_US with an underscore, but forLanguageTag expects en-US with a hyphen.

Mistake 1: an underscore in forLanguageTag. This is the example from the start of the lesson.

Locale bad = Locale.forLanguageTag("en_US");
System.out.println("[" + bad + "]");                   // Output: []
System.out.println(bad.equals(Locale.ROOT));           // Output: true

// Fix: replace the underscore with a hyphen
Locale good = Locale.forLanguageTag("en_US".replace('_', '-'));
System.out.println(good);                              // Output: en_US

Mistake 2: Locale.of does not validate its arguments. The whole string becomes the language code:

Locale wrong = Locale.of("de_DE");
System.out.println(wrong.getLanguage());     // Output: de_de
System.out.println("[" + wrong.getCountry() + "]"); // Output: []

If a locale code comes from outside (settings, a database, an HTTP request), use Locale.Builder: it validates the values and throws IllformedLocaleException, for example on new Locale.Builder().setLanguage("de_DE").

Mistake 3: setDefault as a way to "translate" one screen. Calling Locale.setDefault inside a request handler changes the locale for every thread in the JVM. Pass the locale explicitly: NumberFormat.getInstance(locale), ResourceBundle.getBundle("text", locale).

Frequently Asked Questions

Which one for machine-readable formats: Locale.ROOT or Locale.US?

Both use a dot as the decimal separator, which is why Locale.US is common in older code. But Locale.US holds the settings of real American English users, while Locale.ROOT is designed for operations that do not depend on language. For keys, logs, CSV and protocols choose Locale.ROOT: it also tells the reader of the code that the result is not meant for humans.

Why does Locale have no constant for my country?

The country and language constants were added in Java 1.1 for a small set of locales (only Locale.ROOT came later, in Java 6), and no new country constants have been added since. For other locales use Locale.of("pt", "BR") or Locale.forLanguageTag("pt-BR"). If you need the same locale in many places, declare your own constant: static final Locale BRAZIL = Locale.of("pt", "BR");. The full list of built-in constants is in the section "Locale.US, Locale.FRANCE and other constants".

How do I find out which locales the JVM supports?

Locale.getAvailableLocales() returns an array of all installed locales, and since Java 21 there is also Locale.availableLocales(), which returns a Stream<Locale>. Formatters have their own lists, for example NumberFormat.getAvailableLocales(). You can still create a locale that is not on the list: formatters will use the data of a more general locale.

How do I get a Locale from a string like "en_US"?

Replace the underscore with a hyphen and pass the string to Locale.forLanguageTag(s.replace('_', '-')). The reverse conversion is locale.toLanguageTag(). Why the string without the replacement gives an empty locale is shown in the section "Locale mistakes that fail silently".

Comments

Please log in or register to have a possibility to add comment.