Layer 8Ehrlicher Tech-Support

Java: Zwei gleiche Strings, und == sagt trotzdem false

In Java ist == bei Strings manchmal true und meist false, weil es Referenzen vergleicht, nicht Inhalt. Die Sprachspezifikation nennt equals.

Aufwand:
ca. 10 Min.
Voraussetzung:
Java-Compiler und Laufzeit, Die Java-Sprachspezifikation, Abschnitt 15.21.3, Statische Analyse oder IDE-Warnungen
Gilt für:
Java SE 21 (Java Language Specification, Abschnitt 15.21.3 in der am 2026-10-15 abrufbaren Fassung); das Verhalten ist nicht neu, hier aber nur für diese Fassung belegt

Das Symptom

Der Entwickler vergleicht zwei Strings mit ==, testet kurz mit zwei Literalen im selben Programm, bekommt true und hält die Sache für erledigt. Im Betrieb kommt der eine String aus einem Formular, einer Datei oder einer Datenbank, und derselbe Vergleich liefert false, obwohl beide Texte sichtbar identisch sind. Der Login schlägt fehl, der Statusabgleich greift nie, die Bedingung im if wird nie wahr. Weil es auf dem eigenen Rechner einmal funktioniert hat, wird der Fehler an ganz anderer Stelle gesucht: im Encoding, in unsichtbaren Leerzeichen, in der Datenbank.

Die „Layer 8“-Wahrheit

Die Java-Sprachspezifikation regelt in Abschnitt 15.21.3 unmissverständlich, dass == bei Referenztypen Objektidentität prüft: „At run time, the result of == is true if the operand values are both null or both refer to the same object or array; otherwise, the result is false.“ Für Strings sagt die Spezifikation es sogar ausdrücklich: „While == may be used to compare references of type String, such an equality test determines whether or not the two operands refer to the same String object. The result is false if the operands are distinct String objects, even if they contain the same sequence of characters (§3.10.5, §3.10.6).“

Dass der Schnelltest mit Literalen true ergab, liegt nur daran, dass gleiche Konstanten im selben Programm auf dasselbe Objekt zusammengelegt werden; die Spezifikation verweist dafür auf die Abschnitte 3.10.5 und 3.10.6. Ein zur Laufzeit erzeugter String ist ein neues Objekt, und das ist der Normalfall für alles, was von außen kommt. Der Test hat also einen Sonderfall geprüft und daraus eine Regel gemacht.

Den richtigen Weg nennt die Spezifikation im selben Absatz: „The contents of two strings s and t can be tested for equality by the method invocation s.equals(t).“ Für Vergleiche, bei denen eine Seite null sein kann, bietet sich Objects.equals(a, b) an, für Konstanten das Muster KONSTANTE.equals(eingabe), das nie auf einer null-Referenz aufruft.

Der Fehler ist deshalb so hartnäckig, weil er aussieht wie ein Datenproblem. Niemand sucht die Ursache im Operator, wenn zwei Texte auf dem Bildschirm gleich sind. Aber der Operator hat die Texte nie verglichen. Er hat zwei Adressen verglichen und die richtige Antwort auf eine Frage gegeben, die niemand stellen wollte.

Statische Analyse findet den Vergleich übrigens zuverlässig, weil er rein syntaktisch ist: Zwei Ausdrücke vom Typ String, dazwischen ein doppeltes Gleichheitszeichen. Eine Warnung, die auf Fehler gestellt ist, bricht den Bau ab, bevor der Vergleich je in die Produktion gelangt. Das ist der seltene Fall, in dem ein Werkzeug einen Denkfehler vollständig abfangen kann, und es gibt wenig Gründe, ihm das nicht zu erlauben.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Jeden ==-Vergleich zwischen Strings durch equals ersetzen

    Im Code nach Vergleichen von String-Werten mit == und != suchen und sie durch equals beziehungsweise !equals ersetzen. Bei möglichen null-Werten die Konstante nach vorn stellen oder Objects.equals verwenden, damit der Aufruf nicht selbst mit einer NullPointerException endet. Für Vergleiche ohne Rücksicht auf Groß- und Kleinschreibung gibt es equalsIgnoreCase.

    if ("admin".equals(rolle)) { ... }
    if (Objects.equals(a, b)) { ... }
    
  2. 2.

    Verstehen, warum der Schnelltest true ergab

    Gleiche String-Literale im selben Programm werden zu einem Objekt zusammengelegt, deshalb ist "a" == "a" wahr. Alles, was zur Laufzeit entsteht, etwa durch Einlesen, Zusammensetzen oder substring, ist ein eigenes Objekt. Ein Test, der nur mit Literalen arbeitet, prüft also nicht den Fall, der im Betrieb auftritt. Tests sollten Werte verwenden, die nicht als Literal im Code stehen.

  3. 3.

    Werkzeuge den Fehler finden lassen

    Statische Analyse und IDE-Warnungen erkennen == auf Strings zuverlässig, weil das Muster rein syntaktisch ist. Die Warnung im Build auf Fehler stellen, damit ein solcher Vergleich den Bau abbricht, statt in der Produktion zu warten. Das kostet nichts und fängt jeden künftigen Fall.

  4. 4.

    Dieselbe Regel auf alle Referenztypen anwenden

    Integer, Long, Datumswerte, eigene Klassen: == bedeutet überall dasselbe Objekt. Wer Inhalte vergleichen will, braucht equals und eine Klasse, die equals sinnvoll implementiert. Bei eigenen Klassen gehört hashCode dazu, sonst verhalten sich Mengen und Maps anders als der Vergleich.

Häufige Fragen (FAQ)

?Mein Test mit zwei Literalen ergab true. Warum ist der Vergleich im Betrieb false?

Weil == bei Referenztypen nicht den Inhalt vergleicht, sondern fragt, ob beide Operanden dasselbe Objekt sind. Die Sprachspezifikation sagt in 15.21.3: „At run time, the result of == is true if the operand values are both null or both refer to the same object or array; otherwise, the result is false.“ Zwei gleiche Literale im selben Programm sind dasselbe Objekt, deshalb ergab der Test true. Ein String aus einem Formular, einer Datei oder einer Datenbank ist ein neues Objekt, und derselbe Vergleich ergibt false, obwohl der Text identisch aussieht.

?Steht das für Strings irgendwo ausdrücklich?

Ja, im selben Abschnitt der Spezifikation: „While == may be used to compare references of type String, such an equality test determines whether or not the two operands refer to the same String object. The result is false if the operands are distinct String objects, even if they contain the same sequence of characters (§3.10.5, §3.10.6).“ Das ist keine Eigenheit einer Laufzeit oder einer Version, sondern die definierte Bedeutung des Operators.

?Wie vergleiche ich dann richtig?

Mit equals. Die Spezifikation nennt den Weg direkt im Anschluss: „The contents of two strings s and t can be tested for equality by the method invocation s.equals(t).“ Wer mit null rechnen muss, ruft equals auf der Seite auf, die sicher nicht null ist, etwa KONSTANTE.equals(eingabe), oder verwendet Objects.equals(a, b), das beide Seiten auf null prüft. Die Regel gilt für alle Referenztypen: == fragt nach demselben Objekt, equals nach demselben Inhalt.

?Warum wird der Fehler so oft an der falschen Stelle gesucht?

Weil er sich wie ein Datenproblem anfühlt. Zwei Texte, die auf dem Bildschirm gleich aussehen, gelten als ungleich, also vermutet man unsichtbare Leerzeichen, ein anderes Encoding oder einen Fehler in der Datenbank. Dabei vergleicht der Code gar nicht die Texte. Erst wer die Spezifikation liest, sieht, dass die Frage falsch gestellt war; der Operator hat genau das getan, was er soll, nur nicht das, was gemeint war.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWAnfängerfehlerError: Could not find or load main class

Java: Could not find or load main class

java findet die Startklasse nicht, obwohl die .class-Datei daneben liegt. Gefragt sind voll qualifizierter Klassenname und passender Klassenpfad.

SWSystemzerstörendjava.lang.OutOfMemoryError: Java heap space

OutOfMemoryError: Java heap space

Der Java-Prozess läuft gegen sein Speicherlimit. Mehr Heap zu geben ist oft nur ein Pflaster – meist steckt ein Speicherleck dahinter.