Java example

How to compare strings in Java

7 min read Updated Sep 2026 Runs in an isolated runtime
Quick answer

Use a.equals(b). It compares the characters. == only checks whether both variables point to the same object, so it gives the wrong answer for text built at run time. When the value might be null, put the constant first ("yes".equals(input)) or use Objects.equals(a, b). Use equalsIgnoreCase to ignore case and compareTo to find out which string sorts first.

A Java String is an object, and for objects == compares references, not contents. What makes this confusing is that == often seems to work: string literals are interned, so two "Java" literals really are the same object. The bug only shows up when a string comes from user input, a file, new String or a concatenation, which is exactly when it matters. The sections below cover equality, null safety, ignoring case, ordering and switch. Each example runs on this page: hit Run, then edit the code and run it again.

1equals() vs ==Recommended

a.equals(b) returns true when both strings hold the same characters in the same order. a == b returns true only when a and b are the same object. The JVM keeps one shared copy of every string literal and every compile-time constant (the string pool), so == between literals is true. A string made at run time is a new object, even when its text is identical.

Main.java

Output

Prints a == b: true, a == c: false, a == d: false, a == e: true and a == d.intern(): true, then true true true true from equals. The final variable makes constant + "va" a compile-time constant, so the compiler folds it into the literal "Java". Drop the final and it becomes a run-time concatenation like d. The last two lines are the bug in real code: == missed "yes", then equals matched. The text is right, but nextLine() returns a new object. intern() can make == work again, but you would have to remember it on every path, and String.equals already starts with an identity check, so there is nothing to gain. Keep == for null checks, primitives and enums (see converting a string to an enum).

2Null-safe comparison, and empty or blank strings

equals is a method, so calling it on a null variable throws a NullPointerException. There are two standard fixes. When one side is a constant, put it first: "active".equals(status) is false for null. When both sides can be null, use Objects.equals(a, b) (Java 7+), which treats two nulls as equal. The same thinking applies to "is it empty?": isEmpty() checks for length 0, and isBlank() (Java 11+) also counts a string of only whitespace as blank.

Main.java

Output

Prints false, then NullPointerException from the variable-first call, then true, false, true from Objects.equals. In the grid, only the empty string is isEmpty. Spaces, a tab and newline, and an em space are all isBlank, and " Ada " is neither. The em space row is the one to notice: trim() only removes characters up to U+0020, so trim().isEmpty() says false where isBlank() says true. The last line, true true true false, is the combined check s == null || s.isBlank(). Apache Commons' StringUtils.isBlank wraps the same check, but you don't need a library for it.

3equalsIgnoreCase and contentEquals

a.equalsIgnoreCase(b) compares two strings as if case didn't matter, which is what you want for commands, yes/no answers, HTTP header names and file extensions. It is also null-safe on the argument side. regionMatches(true, ...) does the same for just part of a string. And equals has a second trap: it returns false for anything that isn't a String, so comparing with a StringBuilder needs contentEquals.

Main.java

Output

Prints false, true, false (no exception for a null argument), then true for the header prefix. The ß lines print false, STRASSE, true: equalsIgnoreCase compares one char at a time, and ß only becomes SS when a whole string is upper-cased. The Turkish lines print title and tıtle: in the tr locale a capital I lowercases to a dotless ı. That is why a.toLowerCase().equals(b.toLowerCase()) is a bug waiting for a Turkish server. Use equalsIgnoreCase (it printed true), or pass Locale.ROOT. The last three lines are false, true, true: contentEquals accepts any CharSequence, such as a StringBuilder, without building a new string.

4compareTo, compareToIgnoreCase and Collator for ordering

To ask which string comes first, use a.compareTo(b). It returns a negative number when a sorts before b, zero when they are equal, and a positive number when a sorts after. Only the sign is meaningful: write a.compareTo(b) < 0, never == -1. compareTo compares char codes, which is fine for IDs and keys but not for words shown to people. For those, a java.text.Collator sorts the way a dictionary does.

Main.java

Output

The first four lines print -1, 1, 0 and -2. Today's JDK returns the difference between the first two characters that differ, or between the lengths when one string is a prefix of the other (app vs apple), but other comparators return other magnitudes, so test the sign. Then apple sorts before banana, true (uppercase Z sorts before lowercase a) and false once case is ignored. The three sorted lists show the difference: natural order gives [Cherry, Zebra, apple, banana, eclipse, éclair], CASE_INSENSITIVE_ORDER gives [apple, banana, Cherry, eclipse, Zebra, éclair] (é is still sorted after z), and only the Collator gives the dictionary order, [apple, banana, Cherry, éclair, eclipse, Zebra]. With PRIMARY strength both checks print true. For sorting objects by a string field, see sorting a list of objects by property.

5switch on a String

Since Java 7 you can switch on a String. Each case label is matched with equals, so it is case-sensitive, exact, and not fooled by interning. Java 14 added switch expressions: arrow labels that never fall through, several constants per case, and a result you can return or assign. Java 21 goes further with case null and guarded patterns (case String s when ...) for anything a constant can't express.

Main.java

Output

Prints 0 1 -1: "START" falls to default because matching is case-sensitive. Then stopping, unknown: Stop, and stopping again once the input is lowercased with Locale.ROOT. A null selector throws NullPointerException unless the switch has a case null, and the Java 21 version prints no command, restart-like: reload and other. In the classic form, a missing break falls through into the next case, which is why "stop" and "halt" can share one block. The arrow form makes that sharing explicit with case "stop", "halt". If a block needs several statements before its value, use braces and yield.

6Which should you use?

MethodComparesIf a value is nullBest for
a.equals(b)Characters, exactlyThrows if a is nullThe default equality check
"literal".equals(x)Characters, exactlyfalseA variable against a constant
Objects.equals(a, b)Characters, exactlySafe; two nulls are equalTwo values that may be null
a == bObject identitySafeOnly null checks: s == null
a.equalsIgnoreCase(b)Characters, ignoring casefalse for a null b; throws if a is nullCommands, answers, header names
a.contentEquals(sb)Characters of any CharSequenceThrowsStringBuilder and other CharSequences
a.compareTo(b)Order, by char codeThrowsKeys, IDs, TreeMap order
Collator.compare(a, b)Order, for a languageThrowsSorting words people read
switch (s)equals against constantsThrows, unless case null (Java 21+)Branching on a command or key

Frequently asked questions

What is the difference between == and equals() in Java?

== compares references: for two objects it is true only when both variables point to the same object. equals() compares values, and String.equals checks that both strings have the same characters. So new String("Java") == "Java" is false while new String("Java").equals("Java") is true. For strings, use equals() for content and keep == for null checks. For primitives and enums, == is the right operator.

Why does == sometimes work for strings in Java?

Because of string interning. The JVM keeps one shared copy of each string literal and compile-time constant, so "Java" == "Java" is true, and so is a final constant concatenated with a literal. A string built at run time, by new String, concatenation with a non-final variable or reading input, is a new object, so == is false even when the text matches. Code that passes a test with literals then fails on real input. Watch operator precedence too: System.out.println("equal: " + a == b) prints just false, because the + runs first.

How do I compare strings ignoring case in Java?

Use a.equalsIgnoreCase(b) for equality and a.compareToIgnoreCase(b) or String.CASE_INSENSITIVE_ORDER for sorting. Avoid a.toLowerCase().equals(b.toLowerCase()): without a Locale it uses the default one, and in Turkish "TITLE" lowercases to "tıtle". equalsIgnoreCase compares one char at a time, so "Straße" does not equal "STRASSE". For language-aware matching, use a Collator with SECONDARY strength (ignores case but not accents) or PRIMARY (ignores both).

How do I check if a String is null or empty in Java?

Write s == null || s.isEmpty(), with the null check first so isEmpty() is never called on null. To also treat whitespace-only strings as empty, use s == null || s.isBlank() (Java 11+). isBlank() counts all Unicode whitespace, such as an em space, while s.trim().isEmpty() only strips characters up to U+0020. If you already use those libraries, Apache Commons has StringUtils.isEmpty and StringUtils.isBlank, and Guava has Strings.isNullOrEmpty.

Can I use a switch statement on a String in Java?

Yes, since Java 7. Before that, switch only accepted integer types, chars and enums, which is why so many older answers say it is impossible. The labels are matched with equals, so matching is exact and case-sensitive: lowercase the input with toLowerCase(Locale.ROOT) first if case should not matter. Java 14+ switch expressions (case "stop", "halt" -> ...) avoid fall-through and return a value. A null selector throws NullPointerException unless you add case null (Java 21+).

Why does equals() return false for strings that look the same?

Something invisible differs. Common causes are a trailing newline or space from input ("yes\n".equals("yes") is false; strip() it first), a different type ("Java".equals(new StringBuilder("Java")) is false; use contentEquals), or Unicode normalization: "café" written with a single é is 4 chars, while "cafe" plus a combining accent is 5, and they are not equal. Normalize both with Normalizer.normalize(s, Normalizer.Form.NFC) before comparing.

Run it yourself

Open any of these in the full Java editor to tweak, run and share.

Java playground