toUpperCase() and toLowerCase()

Convert case with an explicit Locale, and avoid the Turkish-i and German-sharp-s bugs that the default locale causes.

Signature
public String toUpperCase(Locale locale)
  • Added in Java Java 1.0

Parameters

ParameterTypeDescription
$localeoptionalLocale= Locale.getDefault()The locale whose case rules apply. The no-argument overloads use Locale.getDefault().

Return value

StringA new String with every char converted to upper case using the rules of the given locale. toLowerCase(Locale) is the mirror method.

Errors

NullPointerException if locale is null.

Changelog

  • PHP Java 1.1The overloads taking a Locale were added.

toUpperCase() and toLowerCase() return a new String with the case changed. Both have two forms: one that takes a Locale, and one with no arguments that uses the JVM's default locale. In server code, always pass a locale.

The Turkish i

Turkish has a dotted and a dotless i in both cases. Under a Turkish locale, "TITLE".toLowerCase() is "tıtle" with a dotless ı, and "title".toUpperCase() is "TİTLE" with a dotted İ. Code that lowercases a header name, a JSON key or an enum constant then fails to match, but only on servers whose default locale is Turkish (or Azerbaijani). That is the worst kind of bug: it passes every test and breaks in one region.

The fix is Locale.ROOT for anything machine-readable: protocol tokens, identifiers, enum names, file extensions, config keys and lookup keys. Use a real user locale only when the result is shown to a person in their language.

Length can change

Case mapping is not always one char to one char. "straße".toUpperCase(Locale.ROOT) is "STRASSE", one char longer, and lowercasing İ outside Turkish gives i plus a combining dot. Never reuse indexes from the original String on the converted one.

Lowercasing for comparison

To compare two values without regard to case, equalsIgnoreCase avoids creating copies. To use a value as a map key or a database lookup, store a normalised form, typically strip().toLowerCase(Locale.ROOT), and normalise incoming values the same way. Keep the original if you need to show it back to the user.

Like every String method, these do not modify the String. email.toLowerCase(Locale.ROOT); on its own does nothing.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Normalising email addresses before using them as a unique key or login lookup.
  • Mapping a status query parameter such as 'active' onto an enum constant.
  • Making file extensions and header names case-insensitive in a request handler.

Frequently asked questions

Why should I use toLowerCase(Locale.ROOT)?

The no-argument version uses the JVM's default locale. Under Turkish or Azerbaijani rules, I and i map to dotless or dotted variants, so identifiers and keys stop matching. Locale.ROOT gives the same result on every server.

Does toUpperCase change the original String?

No. Strings are immutable; it returns a new String. Assign the result, for example s = s.toUpperCase(Locale.ROOT).

More examples

Normalising emails as lookup keys

The stored key and the incoming value go through the same normalisation, so the lookup succeeds whatever the user typed. Strictly, the part before @ may be case-sensitive, but almost every provider ignores case and most sign-in systems do the same.

Example · java
void main() {
    Map<String, String> usersByEmail = new HashMap<>();
    usersByEmail.put("[email protected]", "user-1");

    String login = "  [email protected] ";
    String key = login.strip().toLowerCase(Locale.ROOT);
    IO.println(key);
    IO.println(usersByEmail.get(key));
    IO.println(usersByEmail.get(login));
}
// Output:
// [email protected]
// user-1
// null

Parsing an enum from client input

With Locale.ROOT the value maps to the constant. The Turkish rules produce a dotted capital I, so valueOf fails; that is what the no-argument toUpperCase() does on a server whose default locale is Turkish.

Example · java
enum Status { ACTIVE, DISABLED }

void main() {
    String fromClient = "disabled";
    IO.println(Status.valueOf(fromClient.toUpperCase(Locale.ROOT)));

    String wrong = fromClient.toUpperCase(Locale.forLanguageTag("tr"));
    IO.println(wrong);
    try {
        Status.valueOf(wrong);
    } catch (IllegalArgumentException e) {
        IO.println("no enum constant for " + wrong);
    }
}
// Output:
// DISABLED
// DİSABLED
// no enum constant for DİSABLED

Case changes can change length

Sharp s uppercases to two letters, and a dotted capital I lowercases to i plus a combining dot outside Turkish. Any index or length worked out on the original String is wrong for the converted one.

Example · java
void main() {
    String s = "Gro\u00DFe";
    String upper = s.toUpperCase(Locale.ROOT);
    IO.println(s.length() + " -> " + upper.length());
    IO.println(upper);
    IO.println("\u0130".toLowerCase(Locale.ROOT).length());
}
// Output:
// 5 -> 6
// GROSSE
// 2

Discussion

  • Be the first to comment on this lesson.