diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java index 016bdab539426..2105621d13570 100644 --- a/src/java.base/share/classes/java/time/LocalDate.java +++ b/src/java.base/share/classes/java/time/LocalDate.java @@ -79,6 +79,8 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.io.ObjectStreamField; +import java.io.Serial; import java.io.Serializable; import java.time.chrono.ChronoLocalDate; import java.time.chrono.IsoEra; @@ -142,6 +144,22 @@ public final class LocalDate implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable { + /** + * For backward compatibility of the serialized {@code LocalDate.class} object, + * explicitly declare the types of the serialized fields as defined in Java SE 8. + * Instances of {@code LocalDate} are serialized using the dedicated + * serialized form by {@code writeReplace}. + * @serialField year int The year. + * @serialField month short The month-of-year. + * @serialField day short The day-of-month. + */ + @Serial + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("year", int.class), + new ObjectStreamField("month", short.class), + new ObjectStreamField("day", short.class) + }; + /** * The minimum supported {@code LocalDate}, '-999999999-01-01'. * This could be used by an application as a "far past" date. @@ -178,15 +196,15 @@ public final class LocalDate /** * @serial The year. */ - private final int year; + private final transient int year; /** * @serial The month-of-year. */ - private final short month; + private final transient byte month; /** * @serial The day-of-month. */ - private final short day; + private final transient byte day; //----------------------------------------------------------------------- /** @@ -490,8 +508,8 @@ private static LocalDate resolvePreviousValid(int year, int month, int day) { */ private LocalDate(int year, int month, int dayOfMonth) { this.year = year; - this.month = (short) month; - this.day = (short) dayOfMonth; + this.month = (byte) month; + this.day = (byte) dayOfMonth; } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/MonthDay.java b/src/java.base/share/classes/java/time/MonthDay.java index 1de4fa84d3e54..a25c9beec9522 100644 --- a/src/java.base/share/classes/java/time/MonthDay.java +++ b/src/java.base/share/classes/java/time/MonthDay.java @@ -69,6 +69,8 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.io.ObjectStreamField; +import java.io.Serial; import java.io.Serializable; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; @@ -133,6 +135,20 @@ public final class MonthDay */ @java.io.Serial private static final long serialVersionUID = -939150713474957432L; + + /** + * For backward compatibility of the serialized {@code MonthDay.class} object, + * explicitly declare the types of the serialized fields as defined in Java SE 8. + * Instances of {@code MonthDay} are serialized using the dedicated + * serialized form by {@code writeReplace}. + * @serialField month int The month-of-year. + * @serialField day int The day-of-month. + */ + @Serial + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("month", int.class), + new ObjectStreamField("day", int.class) + }; /** * Parser. */ @@ -144,13 +160,13 @@ public final class MonthDay .toFormatter(); /** - * @serial The month-of-year, not null. + * @serial The month-of-year. */ - private final int month; + private final transient byte month; /** * @serial The day-of-month. */ - private final int day; + private final transient byte day; //----------------------------------------------------------------------- /** @@ -319,8 +335,8 @@ public static MonthDay parse(CharSequence text, DateTimeFormatter formatter) { * @param dayOfMonth the day-of-month to represent, validated from 1 to 29-31 */ private MonthDay(int month, int dayOfMonth) { - this.month = month; - this.day = dayOfMonth; + this.month = (byte) month; + this.day = (byte) dayOfMonth; } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/YearMonth.java b/src/java.base/share/classes/java/time/YearMonth.java index 8ad1172811fe6..b3d1aff7bc249 100644 --- a/src/java.base/share/classes/java/time/YearMonth.java +++ b/src/java.base/share/classes/java/time/YearMonth.java @@ -78,6 +78,8 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.io.ObjectStreamField; +import java.io.Serial; import java.io.Serializable; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; @@ -137,6 +139,20 @@ public final class YearMonth */ @java.io.Serial private static final long serialVersionUID = 4183400860270640070L; + + /** + * For backward compatibility of the serialized {@code YearMonth.class} object, + * explicitly declare the types of the serialized fields as defined in Java SE 8. + * Instances of {@code YearMonth} are serialized using the dedicated + * serialized form by {@code writeReplace}. + * @serialField year int The year. + * @serialField month int The month-of-year. + */ + @java.io.Serial + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("year", int.class), + new ObjectStreamField("month", int.class), + }; /** * Parser. */ @@ -149,11 +165,11 @@ public final class YearMonth /** * @serial The year. */ - private final int year; + private final transient int year; /** - * @serial The month-of-year, not null. + * @serial The month-of-year.. */ - private final int month; + private final transient byte month; //----------------------------------------------------------------------- /** @@ -306,7 +322,7 @@ public static YearMonth parse(CharSequence text, DateTimeFormatter formatter) { */ private YearMonth(int year, int month) { this.year = year; - this.month = month; + this.month = (byte) month; } /** diff --git a/src/java.base/share/classes/java/time/chrono/HijrahDate.java b/src/java.base/share/classes/java/time/chrono/HijrahDate.java index 114a47e4797a8..2d3e4f93e690a 100644 --- a/src/java.base/share/classes/java/time/chrono/HijrahDate.java +++ b/src/java.base/share/classes/java/time/chrono/HijrahDate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -137,11 +137,11 @@ public final class HijrahDate /** * The month-of-year. */ - private final transient int monthOfYear; + private final transient byte monthOfYear; /** * The day-of-month. */ - private final transient int dayOfMonth; + private final transient byte dayOfMonth; //------------------------------------------------------------------------- /** @@ -273,8 +273,8 @@ private HijrahDate(HijrahChronology chrono, int prolepticYear, int monthOfYear, this.chrono = chrono; this.prolepticYear = prolepticYear; - this.monthOfYear = monthOfYear; - this.dayOfMonth = dayOfMonth; + this.monthOfYear = (byte) monthOfYear; + this.dayOfMonth = (byte) dayOfMonth; } /** @@ -287,8 +287,8 @@ private HijrahDate(HijrahChronology chrono, long epochDay) { this.chrono = chrono; this.prolepticYear = dateInfo[0]; - this.monthOfYear = dateInfo[1]; - this.dayOfMonth = dateInfo[2]; + this.monthOfYear = (byte) dateInfo[1]; + this.dayOfMonth = (byte) dateInfo[2]; } //----------------------------------------------------------------------- diff --git a/test/jdk/java/time/test/java/time/TestLocalDate.java b/test/jdk/java/time/test/java/time/TestLocalDate.java index 4b732b0619b62..ef7a10ac68720 100644 --- a/test/jdk/java/time/test/java/time/TestLocalDate.java +++ b/test/jdk/java/time/test/java/time/TestLocalDate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,6 +65,8 @@ import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; import java.time.DateTimeException; import java.time.LocalDate; import java.time.Month; @@ -515,4 +517,18 @@ public void test_minus_QuarterYears(long quarterYears) { .minus(quarterYears, ChronoUnit.MONTHS); assertEquals(t0, t1); } + + // Verify serialized fields types are backward compatible + @Test + public void verifySerialFields() { + var osc = ObjectStreamClass.lookup(LocalDate.class); + for (ObjectStreamField f : osc.getFields()) { + switch (f.getName()) { + case "year" -> assertEquals(f.getType(), int.class, f.getName()); + case "month", + "day" -> assertEquals(f.getType(), short.class); + default -> fail("unknown field in LocalDate: " + f.getName()); + } + } + } } diff --git a/test/jdk/java/time/test/java/time/TestMonthDay.java b/test/jdk/java/time/test/java/time/TestMonthDay.java index b03878be1a553..311a40030c179 100644 --- a/test/jdk/java/time/test/java/time/TestMonthDay.java +++ b/test/jdk/java/time/test/java/time/TestMonthDay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,7 +62,10 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; import java.time.LocalDate; import java.time.Month; import java.time.MonthDay; @@ -144,4 +147,17 @@ void doTest_comparisons_MonthDay(MonthDay... localDates) { } } + + // Verify serialized fields types are backward compatible + @Test + public void verifySerialFields() { + var osc = ObjectStreamClass.lookup(MonthDay.class); + for (ObjectStreamField f : osc.getFields()) { + switch (f.getName()) { + case "month", + "day" -> assertEquals(f.getType(), int.class, f.getName()); + default -> fail("unknown field in MonthDay: " + f.getName()); + } + } + } } diff --git a/test/jdk/java/time/test/java/time/TestYearMonth.java b/test/jdk/java/time/test/java/time/TestYearMonth.java index f02ed96a5d701..0552994b8c255 100644 --- a/test/jdk/java/time/test/java/time/TestYearMonth.java +++ b/test/jdk/java/time/test/java/time/TestYearMonth.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,10 +59,16 @@ */ package test.java.time; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; +import java.time.LocalDate; import java.time.YearMonth; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + /** * Test YearMonth. */ @@ -75,4 +81,16 @@ public void test_immutable() { assertImmutable(YearMonth.class); } + // Verify serialized fields types are backward compatible + @Test + public void verifySerialFields() { + var osc = ObjectStreamClass.lookup(YearMonth.class); + for (ObjectStreamField f : osc.getFields()) { + switch (f.getName()) { + case "year" -> assertEquals(f.getType(), int.class, f.getName()); + case "month" -> assertEquals(f.getType(), int.class, f.getName()); + default -> fail("unknown field in YearMonth: " + f.getName()); + } + } + } }