How to Perform Localization in Java

Localization
Andrew Unsworth
02 Aug 2023

16 min. read

Contents

Localization is a key concern when creating software for a particular region.

What is localization in Java? What mechanisms does Java provide for tailoring software to different languages and locations?

Our guide answers these questions and demonstrates how to create software that suits customers in different locales.

Pro tip: To streamline your localization process in Java, use a professional localization platform – Centus. The platform offers collaborative translation, automation, seamless integration with your tech stack, and quality assurance. Learn more.

What is localization in Java?

What is localization in Java Localization in Java is the art and act of adapting software to the customs and preferences of a specific region or country. The local customs and preferences might dictate the way in which dates, numbers, and currencies are presented.

Common software differences between locales

The most common local software differences include:

The currency symbol

Currency symbols are placed before the digits in some countries and after the digits in other countries.

Example: The US – $100; Poland – 100$

Spacing symbol

Spacing symbols are either present or absent between the digits and currency symbols in different locales. Note that when a spacing symbol is used, it’s usually a non-breaking space.

Example: The US – $100 (no space); China – ¥ 100 (non-breaking space)

Decimal separator

Whereas some countries use a decimal point to mark the difference between monetary units, others prefer commas.

Example: The US – $1.50; Poland – 1, 50 zł

Thousands separator

Different countries use different symbols as thousands separators.

Example: The US – $1,537.23; Poland – 1.537,23 zł

Date format

In some locations, the month is listed first in the six-to-eight digit dates. In others, the month is listed after the day.

Example: The US – 08/31/24; the UK – 31/08/24

Make sure to always pay attention to culturally dependent data when localizing an app in Java.

While some differences in local customs are minor, others are critical. Misused date formats, for example, can result in missed deadlines and appointments.

How to perform localization in Java?

Java is well-suited to localizing software to meet the needs of specific regions and cultures.

Java programming language allows source code to be written on one type of device and then executed on many different types of device. For example, code written on Windows can be executed on macOS or Linux.

It’s, therefore, only natural for the platform-neutral Java language to include mechanisms for localizing software to suit specific needs.

These mechanisms are so numerous and flexible that including them all would be beyond the scope of this article. Therefore, we will cover 3 key classes and provide example code to see how they work:

  • Locale
  • NumberFormat
  • DateTimeFormatter

How to localize the Locale class in Java

The Locale class stores data about languages, countries, and regions. Other classes in your software use Locale objects to format and present data in a manner that’s suitable for the user.

Classes that use Locale objects are referred to as locale-sensitive. The locale sensitive class recognizes and helps other classes render items such as dates, currency, and language for a specific region.

The NumberFormat is a locale sensitive class that uses the Locale object passed to its methods as an argument to format numbers and percentages for a particular region. Later we’ll show how the NumberFormat class uses a Locale object to format numbers and percentages.

Right now let’s see how to create a Locale object and a few handy methods it provides.

The Locale class has three constructors:

  • Locale(String language)
  • Locale(String language, String country)
  • Locale(String language, String country, String variant)

These three constructors provide you with a great deal of versatility when creating Locales. Creating a Locale object to set the language and country for data formatting is as simple as:

Locale loca = new Locale ("en", "GB");

The above code snippet sets the language to English and the region to Great Britain. The country and language codes for various Java platforms can be found online at the Oracle website, so you should be able to find the codes you need.

The “variant” argument is used to denote variations such as a more specific type of dialect, or a specific calendar type, for example.

After creating locale objects, you can either create new ones or re-instantiate the existing ones with new country and language codes, as suits your needs.

You can also create and instantiate a Locale object using the handy method getDefault(), as in the following code snippet:

Locale loca = Locale.getDefault();

The getDefault() method returns a Locale object that is configured using the default language and country options used by the Java Virtual Machine (JVM) on which the code is run. The JVM and the device on which it is run are highly likely to be set up for the physical and cultural locale in which the program is run, so getDefault() is an ideal way to identify the suitable locale.

The following program uses the Locale.getDefault() method to create a new Locale object that is primed with the default language and country code of your PC. It then calls various methods to get information about the default locale data.

The getCountry() method returns a two-letter country code for example, while the toString() method provides the default language and country codes joined by an underscore. The getScript() method doesn’t always provide data but might return a four-letter ISO 15924 script code depending on your default locale settings.

import java.util.*;

class DefaultLocaleInfo {

public static void main(String[] args) {

// Use the Locale.getDefault() method to create and instantiate a new Locale object

Locale loca = Locale.getDefault();

// Call various methods to get info about the default Locale

System.out.println("getCountry(): " + loca.getCountry());

System.out.println("getLanguage(): " + loca.getLanguage());

System.out.println("getScript(): " + loca.getScript());

System.out.println("loca.toString(): " + loca.toString());

}

}

LocaleSelector

We’ll be using a program called LocaleSelector to demonstrate data localization and the use of the Locale, NumberFormat and DateTimeFormatter classes. The program takes plain data stored in a two-dimensional array, applies localized formatting to it, and then prints it to the screen.

The premise is that the data is a tabular record of products, including the products’ name, price, discount to be applied and the date when the products will be available. The program localizes the data so that users from various locations can see the data presented in a manner that suits their language and region. Admittedly, the example is a bit contrived but will suffice for our purposes.

We’ll look at LocaleSelector code snippets over the next couple of sections to see how to create and use various classes. You can scroll down to see the full code listing.

The table below shows the data held in the 2D productData array. product data stored in 2d array

How to localize numbers in Java

From percentages and currency values to the characters used to separate thousands and decimal values, there are many regional differences in the way numerical information is presented to people. Thankfully, Java provides classes to help you format numbers appropriately for specific locales.

The primary one is NumberFormat (in the java.text package), which is an abstract class that provides the methods you need to format currency values, regular numbers, percentages, and so on. There is only one constructor, but chances are that you’ll use one of the NumberFormat class’s methods to create a Number Format instance instead of the constructor.

Formatting Percentages

The code used to format a percentage in LocaleSelector is:

static Locale loca = Locale.getDefault();

static NumberFormat discountFormatter;

discountFormatter = NumberFormat.getPercentInstance(loca);

localData[outerIndex][innerIndex] = discountFormatter.format(productData[outerIndex][innerIndex]);

We declare a Locale field called loca and use the Locale class’s getDefault() method to instantiate loca with the default locale of the system on which the program is run.

We then declare a NumberFormat field called discountFormatter and then instantiate it later in LocaleSelector’s localizeData() method with a call to the NumberFormat class’s getPercentInstance(Locale inLocale) method, with loca used as an argument.

Note that loca is assigned a new locale of the user’s choice in between its declaration and its passing to the getPercentInstance method, so it isn’t the default locale anymore.

The purpose of getPercentInstance is to return a NumberFormat instance that’s ready to apply a percentage format to a value. The formatting happens later in the localizeData method when we call NumberFormat’s format(double number) method and pass it the number to convert to a percentage from the productData array. The String that is returned from this method call is assigned to an element of the localData array for output to the screen.

Example: For the UK locale, this process formats the input value “0.07d” to “7%”.

Formatting currency values

We can use a very similar process to formatting percentages to format currency values for a specific locale, except we call NumberFormat’s getCurrencyInstance(Locale inLocale) method and again pass the loca Locale instance to it as an argument.

The code used to format a currency value in LocaleSelector is:

static NumberFormat priceFormatter;

priceFormatter = NumberFormat.getCurrencyInstance(loca);

localData[outerIndex][innerIndex] = priceFormatter.format(productData[outerIndex][innerIndex]);

We declare a NumberFormat field called priceFormatter and instantiate it later in the program (in LocaleSelector’s localizeData() method) with a call to NumberFormat’s getCurrencyInstance(Locale inLocale) method, passing loca as an argument. We then call NumberFormat’s format(doublenumber) method, passing a value from the productData array as an argument. The String that’s returned from the call to format(double number) is assigned to an element of a second array called localData.

Example: For the Spanish locale, the process formats the input value “2010.45d” to “2.010,45 €”.

As you can see from these two examples, formatting currency values and percentages for different locales requires minimal code.

How to localize dates in Java

It takes a bit more effort to format dates than numbers due to the need to:

  • define the format in which they should be displayed
  • parse an input date to a Date object
  • then format the date and assign it to a String.

However, it can still require just a small block of code to localize a date based on your needs.

The process of localizing LocalDate objects is straightforward:

  1. Create a DateTimeFormatter object with a call to the DateTimeFormatter.ofLocalizedDate(FormatStyle dateStyle) method
  2. Call the LocalDate object’s format(DateTimeFormatter formatter) method
  3. Assign the String returned (which contains the formatted date) to a String object or write it to the screen

The following program illustrates the process:

import java.time.*;

import java.time.format.*;

import java.util.*;

public class DateLocalizer {

public static void main(String[] args) {

LocalDate newDate = LocalDate.now();

System.out.println("newDate: " + newDate);

Locale loca = new Locale("en", "US");

DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(loca);

System.out.println("US short date: " + newDate.format(dateFormatter));

loca = new Locale("en", "GB");

dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(loca);

System.out.println("UK short date: " + newDate.format(dateFormatter));

}

}

You can see that the program outputs a date localized to the US locale and then repeats the localization process with a UK locale. The output from the program is the contents of the LocalDate object newDate, the localized US date and the localized UK date.

Note that the FormatStyle class’s constant SHORT is passed to the ofLocalizedDate method.

FormatStyle has four constants that you can use to format dates and times: FULL, LONG, MEDIUM and SHORT.

FULL provides the most information and SHORT the least. Try using the different constants in the DateLocalizer program listed above to see the difference. Output from the DateLocalizer program In the case of LocaleSelector, the date is stored as an Object in an array, so we need to parse the date so that it can be assigned to a LocalDate object prior to formatting.

The LocaleSelector code is as follows:

static DateTimeFormatter dateFormatter;

static LocalDate tempDate;

String dater = (String) productData[outerIndex][innerIndex];

dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy").withLocale(loca);

tempDate = LocalDate.parse(dater, dateFormatter);

dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(loca);

localData[outerIndex][innerIndex] = tempDate.format(dateFormatter);

Let us detail the process below:

  1. Declare two fields (a DateTimeFormatter object and a LocalDate object) to be instantiated later in the program.
  2. Declare the local String variable dater in LocaleSelector’s localizeData() method and assign dater a date from the productData array, casting the productData element to String as we do so.
  3. Instantiate the DateTimeFormatter object dateFormatter with a call to DateTimeFormatter’s ofPattern(String pattern) method, supplying the pattern string dd/MM/yyyy as an argument.
  4. Instantiate the LocalDate object tempDate with a call to the LcoalDate.parse(CharSequence text, DateTimeFormatter formatter) method, using dater and dateFormatter as arguments.
  5. Call DateTimeFormatter’s ofLocalizedDate(FormatSyle dateStyle) method and withLocale(Locale inLocale) methods to ready dateFormatter for use in formatting the date for a specific locale.
  6. Assign the result of calling tempDate’s format(DateTimeFormatter formatter) method to the relevant element in the localData array.

This code means that the UK-format input date 04/11/2025 is now rendered as 11/04/25 when the US locale is selected in the program, as should be the case. This happens because we told DateTimeFormatter’s ofPattern method that the input date matches the dd/MM/yyyy format. Thus, it knows that the middle digits represent months and can switch them with the ‘day’ digits when the US locale is selected.

You can see the full list of pattern characters used to represent dates and time in the DateTimeFormatter class’s JDK API page. Example output from the LocaleSelector program

LocaleSelector full code listing

The full code for LocaleSelector is as follows:

import java.io.*;

import java.util.*;

import java.text.*;

import java.time.*;

import java.time.format.*;

class LocaleSelector {

// Declarations

static String[] tableHeader = {"Product Name", "Price", "Discount%", "Date Available"};

static Object[][] productData = {

{"Comfy Mattress", 2010.45d, 0.07d, "23/02/2026"},

{"Bonzer Bed", 1450.75d, 0.1d, "04/11/2025"},

{"Top-Brand TV", 2300.00d, 0.4d, "16/05/2025"},

{"My Smart-Fridge", 1899.00d, 0.22d, "25/02/2026"}

};

static Object[][] localData = new Object[4][4];

static Locale loca = Locale.getDefault();

static NumberFormat discountFormatter;

static NumberFormat priceFormatter;

static DateTimeFormatter dateFormatter;

static LocalDate tempDate;

// Set up a means of taking input from the keyboard

static InputStreamReader input = new InputStreamReader(System.in);

static BufferedReader stdIn = new BufferedReader(input);

// Set up a means of writing to standard output

static PrintWriter stdOut = new PrintWriter(System.out, true);

public static void main(String[] args) throws IOException {

boolean loopIterator = true;

while(loopIterator) {

stdOut.println("\nWelome to LocaleSelector! Please choose the locale you'd like to use for this program.");

stdOut.println("Enter 1 for UK English\nEnter 2 for US English\nEnter 3 for French\nEnter 4 for Spanish\n");

stdOut.println("Enter 0 to terminate the program.\n");

int menuChoice = 0;

try {

menuChoice = Integer.valueOf(stdIn.readLine());

} catch (IOException ioe) {

stdOut.println("IOException thrown: " + ioe.toString());

}

catch (NumberFormatException nfe) {

stdOut.println("Please enter a number only.");

stdOut.println("NumberFormatException thrown: " + nfe.toString());

}

// Set the locale using a Switch statement

switch(menuChoice) {

case 0: stdOut.println("You chose to terminate the program\n"); System.exit(0);

case 1: stdOut.println("You chose UK English.\n"); loca = new Locale("en", "GB"); break;

case 2: stdOut.println("You chose US English\n"); loca = new Locale("en", "US"); break;

case 3: stdOut.println("You chose French.\n"); loca = new Locale("fr", "FR"); break;

case 4: stdOut.println("You chose Spanish.\n"); loca = new Locale("es", "ES"); break;

}

try{

localizeData();

} catch (ParseException pe) {stdOut.println(pe);}

outputData();

}

}

private static void localizeData() throws ParseException {

// Set up the means of formatting a currency value

priceFormatter = NumberFormat.getCurrencyInstance(loca);

// Set up the means of formatting a percentage value

discountFormatter = NumberFormat.getPercentInstance(loca);

// Use two FOR loops to access the product array data, localize the data and then write it

// to a second array

for(int outerIndex = 0; outerIndex < 4; outerIndex++){

for(int innerIndex = 0; innerIndex <4; innerIndex++){

// A series of IF statements identify elements to localize, localize the data and then write

// the freshly localized elements to the localData array

// Add product names to the localData array

if(innerIndex == 0){

localData[outerIndex][innerIndex] = productData[outerIndex][innerIndex];

}

// Format prices and add them to the localData array

if(innerIndex == 1) {

localData[outerIndex][innerIndex] = priceFormatter.format(productData[outerIndex][innerIndex]);

}

// Format the discount percentages and add them to the localData array

if(innerIndex == 2) {

localData[outerIndex][innerIndex] = discountFormatter.format(productData[outerIndex][innerIndex]);

}

// Format the dates and add them to the localData array

if(innerIndex == 3) {

// Assign a date in the productData array to a String so that it can be parsed to a Date object

String dater = (String) productData[outerIndex][innerIndex];

// Specify the date format and the locale to instantiate a DateTimeFormatter object

dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy").withLocale(loca);

// Parse the input date string using the dateFormatter object and assign the result to a new LocalDate object

tempDate = LocalDate.parse(dater, dateFormatter);

// call ofLocalizedDate() to specify the date format and locale to be written to

dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(loca);

// Format the date and assign it to an element in the localData array

localData[outerIndex][innerIndex] = tempDate.format(dateFormatter);

}

}

}

}

private static void outputData() {

// This method uses FOR loops to output the contents of the localData array

// to standard output

for(int index = 0; index < 4; index++) {

// Output the table headers

stdOut.print(tableHeader[index] + "\t"); stdOut.flush();

// Add a conditional statement to neaten up output

if(index == 1) {

stdOut.print("\t"); stdOut.flush();

}

}

// Output the data

for(int outerIndex = 0; outerIndex < 4; outerIndex++) {

stdOut.print("\n"); stdOut.flush();

for(int innerIndex = 0; innerIndex < 4; innerIndex++) {

stdOut.print(localData[outerIndex][innerIndex] + "\t"); stdOut.flush();

// Add a conditional statement to neaten up output

if(innerIndex == 2) {

stdOut.print("\t"); stdOut.flush();

}

}

}

stdOut.println("\n");

}

}

Parting tips

It's worth noting that just because you’ve created suitable Locale objects to support the countries and languages in which your customers live and work, it doesn’t mean that data and messages will display correctly on every device or terminal.

This is more of a concern with terminal programs (such as the Windows command line terminal CMD or Netbeans’ output terminal) that are used to display results from programs that display results and information via the standard output.

The percentage sign might be prefaced with an odd symbol, for example, or a thousand-separating comma might appear as a question mark. These issues occur due to the difference in character encoding between the program that displays data from your program and the Java Virtual Machine itself.

One way of mitigating such problems is to have your installation staff note the software environment on which your software will be run to anticipate issues. Similarly, it’s worth alerting your support teams so that they can have solutions ready to help customers resolve issues.

Get the week's best content!

By subscribing, you are agreeing to have your personal information managed in accordance with the terms of Centus’s Privacy Policy ->