Java Instant
Einen Zeitpunkt auf der globalen Zeitlinie in UTC mit Instant darstellen – ideal für Zeitstempel und maschinelle Zeit.
Instant ist ein einzelner Moment auf der globalen Zeitlinie, gespeichert als Nanosekunden seit dem Unix-Epoch (1970-01-01T00:00:00Z). Er hat keine Zeitzone, keinen Kalender, kein Konzept von „welcher Tag ist heute" — nur eine Zahl. Diese Zahl ist per Konstruktion in UTC, sodass zwei Instant-Werte von verschiedenen Maschinen in verschiedenen Zeitzonen direkt verglichen werden können: der mit der kleineren Zahl liegt früher.
Dies ist der Typ für maschinelle Zeitstempel. Logzeilen. Nachrichtenzeitstempel. „Wann hat der Server die Anfrage empfangen." Audit-Trails. Alles, was global sortierbar sein muss und niemals zweideutig darüber sein darf, welchen Tag es darstellt — denn es gibt gar keinen „Tag", nur Sekunden.
Erstellen
Instant now = Instant.now(); // current moment, from System clock
Instant epoch = Instant.EPOCH; // 1970-01-01T00:00:00Z
Instant max = Instant.MAX; // year +1000000000
Instant min = Instant.MIN; // year -1000000000
Instant fromS = Instant.ofEpochSecond(1_700_000_000L);
Instant fromMs = Instant.ofEpochMilli(1_700_000_000_000L);
Instant fromIso = Instant.parse("2025-11-04T19:30:00Z"); // ISO-8601, trailing Z is mandatoryDie Zeichenkettendarstellung endet mit einem buchstäblichen Z (für „Zulu time", militärisch für UTC). Instant.parse("2025-11-04T19:30:00") (ohne Z) ist ein Parse-Fehler — der Typ weigert sich zu raten, welche Zeitzone gemeint ist.
Zwei Fabrikmethoden, die man häufig verwendet:
Instant.ofEpochSecond(epochSec); // long seconds, no nanos
Instant.ofEpochSecond(epochSec, nanos); // with sub-second resolutionDie meisten externen Zeitstempelformate (Unix time(2), syslog, JSON-created_at-Integer) sind Sekunden oder Millisekunden seit dem Epoch. Die Fabrikmethoden ofEpochSecond / ofEpochMilli sind die Standardbrücke.
Auflösung
Instant hat Nanosekunden-Präzision (1 Sekunde = 1.000.000.000 ns). Auf den meisten Systemen hat die zugrunde liegende Uhr eine geringere Auflösung — typischerweise Millisekunden, auf modernem Linux Mikrosekunden. Instant.now() gibt einen Wert mit der verfügbaren Auflösung zurück; die nicht genutzten Nanosekunden sind null.
Zugriffsmethoden:
long seconds = inst.getEpochSecond(); // long; can go past 2038
int nanos = inst.getNano(); // 0-999_999_999
long milli = inst.toEpochMilli(); // throws if out of long rangetoEpochMilli ist die verlustbehaftete Konvertierung: Nanosekunden werden auf Millisekunden gekürzt. Für Logzeilen und JSON-Zeitstempel ist das meist akzeptabel; für hochfrequente Ereignisaufzeichnungen sollte man getEpochSecond + getNano separat verwenden.
Kein Kalender
Instant.getDayOfMonth() existiert nicht. Ebenso wenig getYear, getHour oder andere Kalender-Accessoren. Der Typ weiß es schlicht nicht — Kalenderinformationen erfordern eine Zeitzone, und Instant hat keine. Wenn man wissen möchte „Welche Stunde war es in New York, als das passierte", muss man zuerst eine Zeitzone anhängen:
ZonedDateTime zdt = inst.atZone(ZoneId.of("America/New_York"));
int hour = zdt.getHour();
LocalDate date = zdt.toLocalDate();atZone(zone) ist die Brücke in die andere Richtung von ZonedDateTime.toInstant(). Zusammen ergeben sie den vollständigen Roundtrip: Moment ↔ zeitzonenbeschrifteter Wert. Siehe Java ZonedDateTime für die Kalenderseite dieser Paarung.
Arithmetik
Gleiche fließende Schreibweise:
inst.plusSeconds(60);
inst.plusMillis(500);
inst.plusNanos(1_000_000);
inst.plus(Duration.ofMinutes(15)); // any Duration
inst.minus(Duration.ofDays(1)); // exactly 24h * 3600sKein plusDays auf Instant (im Kalendersinne). Es gibt plus(amount, ChronoUnit), und ChronoUnit.DAYS funktioniert, weil das JDK einen Day als genau 24 Stunden Sekunden für Instant definiert. Das entspricht nicht einem Kalendertag bei Sommerzeit, weshalb Instant auch nicht vorgibt, einer zu sein.
inst.plus(1, ChronoUnit.DAYS); // exactly 86_400 seconds
inst.plus(7, ChronoUnit.DAYS); // exactly 604_800 secondsFür kalenderförmige Operationen („einen Monat später in der Zeitzone des Nutzers") geht man über ZonedDateTime:
Instant later = inst.atZone(zone).plusMonths(1).toInstant();Vergleichen
inst1.isBefore(inst2);
inst1.isAfter(inst2);
inst1.equals(inst2);
inst1.compareTo(inst2);Instant implementiert Comparable<Instant> mit natürlicher Reihenfolge nach Epoch-Sekunde und dann Nanosekunden. equals ist unkompliziert: gleiche Sekunde und gleiche Nanosekunden.
Abstände
Duration d = Duration.between(start, end); // a Duration
long millis = ChronoUnit.MILLIS.between(start, end);
long days = ChronoUnit.DAYS.between(start, end); // 24h-equivalent daysFür maschinelle Zeitstempel sind all das exakte Berechnungen — es gibt keine Kalenderuneindeutigkeit. ChronoUnit.MONTHS.between(start, end) auf Instant-Werten wirft eine Ausnahme, weil Monate keine konstante Länge in Sekunden haben: ohne Zeitzone hat der Rechner keine Möglichkeit zu wissen, welcher Monat diese Sekunden enthält. Das ist das richtige Fehlverhalten.
java.util.Date-Brücke
Alter Code verwendet java.util.Date. Die Konvertierungen sind direkt:
Date legacy = Date.from(inst); // Instant -> Date
Instant back = legacy.toInstant(); // Date -> InstantDate ist intern ein Wrapper um ein Epoch-Millisekunden-long, sodass der Roundtrip verlustlos abzüglich Nanosekunden ist (Date hat Millisekunden-Präzision, Instant Nanosekunden-Präzision). Das Kapitel Legacy Date behandelt die Migration im Detail.
Warum alles Interne Instant sein sollte
Die Empfehlung, die sich aus zehn Jahren java.time in der Produktion ergeben hat:
- Intern
Instantverwenden. Speicherung, Vergleich, Logging, Nachrichtenzeitstempel, überall wo der Wert zwischen Maschinen fließt. - An der Grenze — bei der Anzeige für Nutzer, bei der Entgegennahme von Nutzereingaben — in
ZonedDateTimeoderLocalDateTimemit der richtigen Zeitzone für den Kontext umwandeln.
Das trennt „was wirklich passiert ist" von „wie es beschriftet ist". Ein Fehler an der Grenze (falsche Zeitzone) lässt die internen Werte korrekt; ein Fehler im Typsystem, der LocalDateTime intern fließen lässt, führt zu Zeitstempeln, die stillschweigend in verschiedenen Zeitzonen liegen.
Ein durchgearbeitetes Beispiel: ein kleines Ereignisprotokoll
Das folgende Programm zeichnet eine Folge von Ereignissen als Instant-Werte auf, berechnet Abstände zwischen Ereignissen, demonstriert die Kalendergrenze durch das Anhängen einer Zeitzone zur Anzeige, zeigt die Legacy-Date-Brücke und veranschaulicht schließlich die „kein Kalender"-Regel, indem gezeigt wird, dass ChronoUnit.MONTHS.between auf zwei Instant-Werten eine Ausnahme wirft.
Was aus dem Lauf mitzunehmen ist:
Instant.parse("2025-11-04T19:30:00Z")wurde nur geparst, weil dasZam Ende vorhanden war. Lässt man dasZweg, schlägt das Parsen fehl — der Typ besteht darauf zu wissen, dass die Zeichenkette UTC ist. Andere Zeitzonen müssen überZonedDateTime.parse(oder peratZone) angegeben werden.- Die Ereignisfolge verwendete
Duration.between(...). Jedes Ergebnis war eine saubere ganzzahlige Millisekundenanzahl — keine Zeitzonenverwirrung, keine Sommerzeit, keine Kalenderarithmetik. Daher gehören serverseitige Zeitangaben inInstant: die Arithmetik ist unter der Haube nur eine Subtraktion vonlong-Werten. - Der Block „gleicher Moment, zwei Zonenbezeichnungen" druckte
ZonedDateTime-Werte, die verschieden aussahen, aber derselbeInstantwaren.atZone(...)ist fürInstantrein eine Anzeigeoperration. Wenn man Kalenderrechnung durchführen möchte (nächster Monat, Wochenende), tut man das auf demZonedDateTime, ruft dann.toInstant()auf, um zurückzukehren. Date.from(inst)undlegacy.toInstant()waren verlustlos abzüglich Nanosekunden.Dateträgt nur Millisekunden, sodass ein Roundtrip überDatedie Sub-ms-Präzision kürzt. Für die meisten Protokollierungen ist das in Ordnung; für hochpräzise Ereignisaufzeichnungen bleibt man durchgängig beiInstantund macht keinen Roundtrip überDate.ChronoUnit.MONTHS.between(a, b)warfUnsupportedTemporalTypeException. Das ist das richtige Fehlverhalten: Monate sind keine konstanten Sekundenmengen, und das JDK weigert sich, eine Antwort zu erfinden. Der Wechsel zuZonedDateTimelieferte die fehlende Zeitzone, und derselbe Aufruf funktionierte. Das Muster ist allgemein: kalenderförmige Operationen benötigen eine Zeitzone, und das Typsystem zwingt einen, diese explizit anzugeben.
Was als Nächstes kommt
Instant ist der Moment. Die nächsten zwei Kapitel behandeln die Zeitlängen zwischen Momenten: Java Duration für „X Sekunden, Y Nanosekunden"-Messungen und Java Period für „X Jahre, Y Monate, Z Tage" als Kalenderlängen. Zusammen bilden sie die Grundlage dafür, „eine Stunde später" vs. „einen Monat später" ohne Verlust auszudrücken.