Back to Blog
Java

Java String compareTo: Usage, Behavior, and Pitfalls

Understand how Java's String.compareTo works, what its return value means, and how to use it safely in sorting and ordering logic.

String comparisonLexicographic orderJava String APISortingNull handling
Two Java string objects being compared with a scale showing lexicographic order

When you call compareTo on two String objects, you are asking for a lexicographic comparison. The method returns an integer that indicates whether the first string is less than, equal to, or greater than the second string, based on the UTF-16 code unit of each char. This behavior is fundamental to sorting and ordering in Java, but its subtleties often cause confusion.

The method signature is public int compareTo(String anotherString). It returns a negative integer if the calling string precedes the argument, zero if they are equal, and a positive integer if the calling string follows the argument. For String, the Javadoc describes the exact return value, but application code should rely on the sign rather than the magnitude. The general Comparable contract intentionally leaves the magnitude unspecified.

What compareTo Actually Returns

For String, compareTo does not return only -1, 0, or 1. It returns the difference between the first mismatched character values, or the difference in lengths if all characters match up to the shorter string. Consider:

String a = "apple"; String b = "apricot"; int result = a.compareTo(b);

The first mismatch occurs at index 1: 'p' (code unit 112) versus 'r' (code unit 114). The method returns 112 - 114 = -2. So result is negative, meaning a precedes b. If the strings are identical up to the shorter length, the difference in lengths is returned. For example, "cat".compareTo("catalog") returns 3 - 7 = -4.

This approach allows a single pass through the characters without allocating a temporary object. For ordering logic, what matters is the sign: negative, zero, or positive.

Lexicographic Order and Character Comparison

The comparison is based on the UTF-16 char values in the string. For the basic Latin alphabet, this matches ASCII order, so uppercase letters (65–90) come before lowercase letters (97–122). That is why "Zebra".compareTo("apple") returns a negative value: 'Z' (90) is less than 'a' (97).

This ordering is not locale-sensitive. For most Latin-script languages, the order of letters with diacritics may differ from what a human expects. For example, "é".compareTo("z") returns a positive value because the code unit for 'é' (233) is greater than that for 'z' (122). If you need locale-aware ordering, use java.text.Collator.

The comparison is also case-sensitive. "hello".compareTo("Hello") returns a positive value because 'h' (104) is greater than 'H' (72). If you need a case-insensitive comparison, compareToIgnoreCase is the direct alternative.

Case Sensitivity and Locale Issues

compareToIgnoreCase is convenient, but it still does not handle locale-specific rules. It applies Java's default Unicode case mapping, not collation rules. For example, Turkish has distinct ordering rules for dotted and dotless I, and a generic case-insensitive comparison may not produce the order a user expects.

String s1 = "Java"; String s2 = "java"; int result = s1.compareToIgnoreCase(s2); // 0

This is useful when you only need to compare strings without regard to case, but for ordering in a specific locale, java.text.Collator is the safer choice.

Null Handling and Defensive Coding

Calling compareTo on a null reference throws NullPointerException. There is no built-in null handling. If you compare strings that may be null, decide how to treat null values. A common pattern is to treat null as less than any non-null string:

public int compareWithNull(String a, String b) { if (a == null && b == null) return 0; if (a == null) return -1; if (b == null) return 1; return a.compareTo(b); }

This gives a consistent total order. When sorting a list that may contain nulls, you can use Comparator.nullsFirst(String::compareTo) or Comparator.nullsLast(String::compareTo) instead of writing the null checks manually.

Comparing Strings with compareTo vs equals

The equals method on String checks content equality and returns a boolean. compareTo returns an integer and defines an ordering. Use equals when you only need to know whether two strings are the same. Use compareTo when you need to sort, order, or determine relative position.

A common readability issue is using compareTo to test equality:

if (str1.compareTo(str2) == 0) { ... }

This works, but str1.equals(str2) is clearer. Because String is final, subclass overriding is not a practical concern for either method; the reason to prefer equals is readability and consistency with other objects.

Performance and Memory Considerations

compareTo scans the two strings until it finds a difference or one string ends. In the worst case, it examines every character of the shorter string, so a single comparison is O(n) with respect to the number of characters inspected. For repeated comparisons, such as in sorting, the total cost can be significant.

compareTo does not allocate temporary objects, and it does not use the string's cached hash code. For large collections, consider whether a custom comparator can avoid unnecessary work, but the standard implementation is already optimized for typical Java workloads.

One subtle point: if two strings share a common prefix, the method still scans the entire prefix. There is no shortcut for common prefixes.

Using compareTo in Sorting and Ordering

The most common use of compareTo is as the natural ordering for String objects. When you call Collections.sort(list) on a list of strings, the default comparator uses compareTo. This produces a deterministic, case-sensitive, lexicographic order.

If you need a different order, you can supply a custom Comparator that uses compareTo as a building block. For example, to sort by length and then alphabetically:

Comparator<String> byLengthThenAlpha = Comparator.comparingInt(String::length) .thenComparing(String::compareTo);

For reverse order, use Comparator.reverseOrder() or Collections.reverseOrder(); both rely on String's natural ordering.

Common Pitfalls and Edge Cases

One edge case is supplementary Unicode characters, such as emoji. compareTo compares char values, which are UTF-16 code units. A supplementary character is represented by a surrogate pair, so the comparison may not reflect code-point order. For example, 😀 (U+1F600) is represented as two char values, 0xD83D and 0xDE00. When compared with another string, those surrogate values are compared, which may not match an order based on Unicode code points. If you need code-point-aware ordering, use String.codePointAt and a custom comparator.

Another pitfall is depending on a particular nonzero return value. The general Comparable contract only requires a negative, zero, or positive integer. Although String.compareTo describes the calculated value, robust code checks only the sign.

Finally, because compareTo and compareToIgnoreCase define different orderings, mixing them in one sort can violate the comparator contract. Choose one comparison mode for the entire operation.

Java String compareTo: Practical Usage and Code Examples | RYUSLOG DEV