Layer 8Ehrlicher Tech-Support

Java: 2147483647 + 1 ergibt -2147483648, ohne Meldung

2147483647 + 1 ergibt in Java -2147483648, ohne Exception. Die Sprachspezifikation sagt: Die Ganzzahloperatoren zeigen Überlauf in keiner Weise an.

Aufwand:
ca. 20 Min.
Voraussetzung:
Java-Compiler und Laufzeit, Die Java-Sprachspezifikation, Abschnitte 4.2.1, 4.2.2 und 15.18.2, Tests mit Grenzwerten
Gilt für:
Java SE 21 (Java Language Specification in der am 2026-10-15 abrufbaren Fassung), Math.addExact ist über die API-Dokumentation der Klasse Math für Java SE 21 belegt (seit Java 1.8); multiplyExact und toIntExact stehen auf derselben Seite, sind hier aber nicht wörtlich zitiert

Das Symptom

Der Entwickler rechnet Summen, Millisekunden, Bytezahlen oder Centbeträge in int und verlässt sich darauf, dass Java bei einem zu großen Ergebnis abbricht oder wenigstens warnt. Tut es nicht: Ab 2147483647 kippt das Ergebnis lautlos ins Negative, eine Dateigröße wird negativ, ein Zeitunterschied springt zurück, eine Summe wird kleiner statt größer. Der Fehler fällt nicht beim Rechnen auf, sondern erst irgendwo stromabwärts, wo ein negativer Wert keinen Sinn ergibt, und der Verdacht fällt auf die Eingabedaten.

Die „Layer 8“-Wahrheit

Die Java-Sprachspezifikation legt in Abschnitt 4.2.2 fest: „The integer operators do not indicate overflow or underflow in any way.“ Das ist kein Versäumnis, sondern eine Entscheidung, und sie gilt für jede Addition, Subtraktion und Multiplikation auf int und long. Abschnitt 15.18.2 beschreibt, was stattdessen passiert: „If an integer addition overflows, then the result is the low-order bits of the mathematical sum as represented in some sufficiently large two’s-complement format. If overflow occurs, then the sign of the result is not the same as the sign of the mathematical sum of the two operand values.“ Der Wertebereich von int ist nach Abschnitt 4.2.1 „from -2147483648 to 2147483647, inclusive“; was darüber hinausgeht, beginnt am anderen Ende wieder.

Eine Ausnahme wirft Java bei Ganzzahlen nur in einem Fall, der Division durch null. Alles andere ist ein definiertes Ergebnis, und ein definiertes Ergebnis löst keinen Alarm aus. Wer eine Fehlermeldung will, muss sie bestellen: die Exact-Methoden der Klasse Math werfen bei Überlauf eine ArithmeticException. Die API-Dokumentation beschreibt addExact als Methode, die „Returns the sum of its arguments, throwing an exception if the result overflows an int.“ und nennt als Ausnahme „ArithmeticException

  • if the result overflows an int“; vorhanden ist sie seit Java 1.8. Für Beträge und Zähler, die groß werden können, ist long die erste Wahl, für Geld BigDecimal, und bei Produkten muss der erste Operand bereits long sein, sonst rechnet Java in int und wandelt erst das falsche Ergebnis um.

Die Erwartung, dass eine sichere Sprache auch vor großen Zahlen schützt, ist nachvollziehbar und falsch. Java sichert Speicher und Typen; die Ganzzahlen folgen dem Zweierkomplement, und das Zweierkomplement kennt keinen Fehler, nur einen Rand. Wer an den Rand gerät, merkt es nicht an der Rechnung, sondern an dem Ort, an dem ein Minus steht, wo keines sein kann.

Dass andere Sprachen es anders halten, macht die Sache nicht einfacher. Wer von einer Umgebung kommt, die bei Überlauf abbricht oder automatisch auf beliebig große Zahlen wechselt, bringt die Erwartung mit, dass eine Rechnung entweder stimmt oder scheitert. In Java gibt es eine dritte Möglichkeit: Sie stimmt modulo zwei hoch zweiunddreißig, und das sieht von außen aus wie Scheitern mit Verzögerung. Die Spezifikation versteckt das nicht; sie sagt es in einem Satz, der nur selten gelesen wird, weil niemand die Spezifikation aufschlägt, um zu erfahren, was plus bedeutet.

Die echte, verifizierte Lösung

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

  1. 1.

    Prüfen, ob der Wertebereich von int überhaupt reicht

    Für jede Größe, die in int gerechnet wird, fragen, ob sie 2147483647 überschreiten kann. Millisekunden seit Mitternacht ja, nach etwa 24 Tagen; Bytes einer Datei ja, ab zwei Gigabyte; Centbeträge ja, ab gut 21 Millionen Euro. Wer die Frage nicht sicher mit nein beantworten kann, wechselt den Typ.

  2. 2.

    long oder BigDecimal verwenden und Zwischenergebnisse hochstufen

    Große Zähler und Beträge in long führen, Geld in BigDecimal. Bei Produkten die Rechnung von Anfang an in long ausführen, nicht erst das Ergebnis umwandeln: 1L * a * b rechnet in long, (long) (a * b) rechnet in int und wandelt den bereits übergelaufenen Wert um.

    long bytes = 1L * bloecke * blockgroesse;
    
  3. 3.

    Überlauf als Fehler behandeln, wo er einer sein soll

    Wo ein Überlauf nicht vorkommen darf, die Exact-Varianten der Klasse Math einsetzen, die eine ArithmeticException werfen, statt lautlos umzubrechen. Dann endet das Programm an der Stelle, an der der Fehler entsteht, und nicht dort, wo der negative Wert zufällig auffällt.

    int summe = Math.addExact(a, b);
    
  4. 4.

    Tests mit Grenzwerten schreiben

    Jede Rechnung mit Integer.MAX_VALUE, Integer.MIN_VALUE, -1 und 0 testen. Ein Vorzeichenwechsel in einem solchen Test ist der Überlauf, den die Spezifikation beschreibt, und er ist im Test billiger als in der Buchhaltung.

Häufige Fragen (FAQ)

?Java ist doch eine sichere Sprache. Warum wirft ein zu großes Ergebnis keine Exception?

Weil die Sprache es so festlegt. Abschnitt 4.2.2 der Spezifikation sagt in einem Satz: „The integer operators do not indicate overflow or underflow in any way.“ Sicher ist Java bei Speicherzugriffen und Typen, nicht bei der Größe von Ganzzahlen. Eine ArithmeticException gibt es bei der Ganzzahldivision durch null, nicht beim Überlauf; der ist ein definiertes, lautloses Ergebnis, kein Fehler.

?Was kommt beim Überlauf genau heraus?

Abschnitt 15.18.2 beschreibt es: „If an integer addition overflows, then the result is the low-order bits of the mathematical sum as represented in some sufficiently large two's-complement format. If overflow occurs, then the sign of the result is not the same as the sign of the mathematical sum of the two operand values.“ Die Summe wird also abgeschnitten, und das Vorzeichen kippt. Aus 2147483647 + 1 wird -2147483648, weil der Wertebereich von int nach Abschnitt 4.2.1 „from -2147483648 to 2147483647, inclusive“ reicht und dahinter wieder vorn beginnt.

?Wo passiert das in echten Programmen?

Überall, wo etwas gezählt oder summiert wird, das größer werden kann als gut zwei Milliarden: Millisekunden seit einem Zeitpunkt, Bytezahlen von Dateien, Summen in Cent, Produkte zweier mittelgroßer Zahlen. Eine Dateigröße wird negativ, ein Zeitunterschied springt zurück, eine Summe wird kleiner statt größer. Der Fehler fällt nicht beim Rechnen auf, sondern erst irgendwo stromabwärts, wo ein negativer Wert keinen Sinn ergibt, und dort fällt der Verdacht auf die Eingabedaten.

?Wie bekomme ich eine Fehlermeldung, wenn ich eine will?

Indem man sie bestellt. Für Beträge und Zähler, die groß werden können, ist long die erste Wahl, für Geld BigDecimal. Wer bei int bleiben muss und einen Überlauf als Fehler behandelt sehen will, verwendet die Methoden der Klasse Math mit dem Zusatz Exact. Die API-Dokumentation sagt zu addExact: „Returns the sum of its arguments, throwing an exception if the result overflows an int.“ Geworfen wird eine ArithmeticException, und die Methode gibt es laut derselben Seite seit Java 1.8. Die Operatoren selbst werden nie eine werfen, das ist der Punkt.

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

SWLästigUnsupportedClassVersionError

Java: UnsupportedClassVersionError

Das Programm wurde mit einem neueren Java kompiliert, als es ausführt? Diese Meldung nennt sogar die Versionsnummern — man muss sie nur übersetzen.

SWLästig

jcmd findet keine Java-Prozesse

jcmd -l listet nichts oder nur sich selbst, obwohl die JVM läuft. Schuld ist fast immer der falsche Benutzer – und sudo macht es schlimmer, nicht besser.