The LocalDate class in Java represents a calendar date without a time of day and without a time zone, such as 2026-05-14. It belongs to the java.time package, which replaced java.util.Date and Calendar in Java 8, and a LocalDate object is immutable and thread-safe.
We use LocalDate for values that are a whole day, such as a birthday, a public holiday, an invoice due date or the days of a leave request. For a time of day we use LocalTime, and for a moment in a specific place we use ZonedDateTime.
The following example creates a gym membership start date and runs the operations this article covers, and each comment shows the result of its line.
LocalDate joined = LocalDate.of(2026, 1, 31); // 2026-01-31
LocalDate trialEnds = joined.plusDays(14); // 2026-02-14
LocalDate renewal = joined.plusMonths(1); // 2026-02-28, February has no 31st
LocalDate parsed = LocalDate.parse("2026-03-01"); // 2026-03-01
boolean afterRenewal = parsed.isAfter(renewal); // true
DayOfWeek day = renewal.getDayOfWeek(); // SATURDAY
String text = renewal.format(DateTimeFormatter.ofPattern("d MMM uuuu", Locale.US)); // "28 Feb 2026"
We can see that every method returns a new LocalDate and leaves joined unchanged. The sections start with creating, parsing and formatting dates and continue with date arithmetic and its end-of-month rule, comparisons, a membership renewal example and conversions to the other date types.
1. Overview of the LocalDate Class
A LocalDate stores three numbers, the year, the month and the day of the month, in the ISO-8601 calendar. Every other value, such as the day of the week or whether the year is a leap year, is calculated from these three fields. The value has no time and no zone, so 2026-05-14 means the same calendar day in Tokyo and in New York.

Choosing the right class matters, because each java.time class answers a different question. The table compares LocalDate with its closest relatives.
| Class | Holds | Example value | Typical use |
|---|---|---|---|
| LocalDate | Date only | 2026-05-14 | Birthdays, holidays, due dates |
| LocalTime | Time only | 09:30 | Opening hours, alarm times |
| LocalDateTime | Date and time, no zone | 2026-05-14T09:30 | Appointments in one known place |
| ZonedDateTime | Date, time and time zone | 2026-05-14T09:30+02:00[Europe/Paris] | Meetings across time zones |
| Instant | A point on the UTC timeline | 2026-05-14T07:30:00Z | Log entries and event timestamps |
A LocalDate is immutable, so methods such as plusDays() return a new object. That makes the class thread-safe, and we can share one instance between threads or keep it in a static constant.
2. Creating Instances of LocalDate
We create a LocalDate in two situations, either for today’s date or for a date we already know, such as one read from a form or a database. All factory methods are static, because the class has no public constructor.
2.1. Getting Today’s Date
The now() method returns today’s date in the JVM’s default time zone. Near midnight, the date differs between zones, so we pass a ZoneId when the business rule belongs to a specific place.
LocalDate today = LocalDate.now(); // today in the default zone
LocalDate todayInTokyo = LocalDate.now(ZoneId.of("Asia/Tokyo")); // today in Tokyo
Code that calls now() is hard to test, because the result changes every day. Passing a fixed Clock makes the date repeatable in unit tests, and production code passes Clock.systemDefaultZone().
Clock clock = Clock.fixed(Instant.parse("2026-05-14T10:00:00Z"), ZoneOffset.UTC);
LocalDate testToday = LocalDate.now(clock); // 2026-05-14
2.2. Creating a LocalDate From Given Values
The overloaded of() method takes the year, the month and the day, where the month is either a number from 1 to 12 or a Month constant. The other factory methods build a date from a day count or from an Instant.
LocalDate fromNumbers = LocalDate.of(2026, 5, 14); // 2026-05-14
LocalDate fromEnum = LocalDate.of(2026, Month.MAY, 14); // 2026-05-14
LocalDate fromEpochDay = LocalDate.ofEpochDay(18823); // 2021-07-15, days since 1970-01-01
LocalDate fromYearDay = LocalDate.ofYearDay(2022, 37); // 2022-02-06, the 37th day of 2022
LocalDate fromInstant = LocalDate.ofInstant(Instant.parse("2026-05-14T22:30:00Z"), ZoneId.of("Asia/Kolkata")); // 2026-05-15
We write months and days without a leading zero. Java reads a literal such as 08 as an octal number, so LocalDate.of(2026, 08, 1) does not compile. An impossible date compiles but throws a DateTimeException at runtime.
LocalDate invalid = LocalDate.of(2023, 2, 29); // DateTimeException: Invalid date 'February 29' as '2023' is not a leap year
3. Parse a Date String Into LocalDate
The parse(CharSequence) method reads text in the ISO format yyyy-MM-dd, which is also the format that toString() prints. For any other format, we pass a DateTimeFormatter that describes the pattern of the text.
LocalDate iso = LocalDate.parse("2026-02-06"); // 2026-02-06
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MMM-yyyy", Locale.US);
LocalDate custom = LocalDate.parse("6-Feb-2026", formatter); // 2026-02-06
LocalDate wrong = LocalDate.parse("06/02/2026"); // DateTimeParseException: Text '06/02/2026' could not be parsed at index 0
Text from users and files can be in the wrong format, so we catch DateTimeParseException and return an empty Optional instead of letting the request fail. More patterns and the strict resolver are covered in converting a String to LocalDate.
static Optional<LocalDate> parseDate(String text) {
try {
return Optional.of(LocalDate.parse(text.strip()));
} catch (DateTimeParseException e) {
return Optional.empty();
}
}
Optional<LocalDate> good = parseDate(" 2026-05-14 "); // Optional[2026-05-14]
Optional<LocalDate> bad = parseDate("14.05.2026"); // Optional.empty
4. Format LocalDate to String
The format(DateTimeFormatter) method turns a date into text in any pattern, and the toString() method always gives the ISO form. Month names depend on the locale, so we pass a Locale to the formatter instead of relying on the server default.
LocalDate date = LocalDate.of(2026, 5, 14);
String iso = date.toString(); // "2026-05-14"
String slashes = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); // "14/05/2026"
String words = date.format(DateTimeFormatter.ofPattern("EEEE, d MMMM uuuu", Locale.US)); // "Thursday, 14 May 2026"
String german = date.format(DateTimeFormatter.ofPattern("d. MMMM uuuu", Locale.GERMANY)); // "14. Mai 2026"
A DateTimeFormatter is immutable and thread-safe, so we create it once and keep it in a constant. The full list of pattern letters is in formatting LocalDate to String.
5. Add and Subtract Days, Weeks, Months and Years
The plusDays(), plusWeeks(), plusMonths() and plusYears() methods add an amount and return a new date, and the matching minus methods subtract it. The generic plus(long, TemporalUnit) and plus(Period) methods do the same with a unit or a period object.
LocalDate start = LocalDate.of(2026, 5, 14);
LocalDate nextWeek = start.plusWeeks(1); // 2026-05-21
LocalDate lastMonth = start.minusMonths(1); // 2026-04-14
LocalDate inThreeYears = start.plusYears(3); // 2029-05-14
LocalDate tenDaysAgo = start.minus(10, ChronoUnit.DAYS); // 2026-05-04
LocalDate later = start.plus(Period.of(0, 2, 5)); // 2026-07-19
When the target month is shorter, plusMonths() and plusYears() clamp the day to the last valid day of that month instead of throwing an exception. January 31 plus one month is February 28, and February 29 plus one year is February 28 of the next year.
LocalDate endOfJanuary = LocalDate.of(2026, 1, 31);
LocalDate february = endOfJanuary.plusMonths(1); // 2026-02-28
LocalDate leapDay = LocalDate.of(2028, 2, 29);
LocalDate nextYear = leapDay.plusYears(1); // 2029-02-28
To jump to a fixed position, such as the first day of the month or the next Monday, we use with() and a TemporalAdjuster. More recipes are in adding days, months and years to a date.
LocalDate day = LocalDate.of(2026, 5, 14);
LocalDate firstOfMonth = day.with(TemporalAdjusters.firstDayOfMonth()); // 2026-05-01
LocalDate nextMonday = day.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 2026-05-18
LocalDate sameDayIn2030 = day.withYear(2030); // 2030-05-14
6. Reading the Year, Month and Day
The getter methods return the stored fields and the values calculated from them. The getMonth() and getDayOfWeek() methods return the enums Month and DayOfWeek, and getMonthValue() returns the month as a number from 1 to 12.
LocalDate date = LocalDate.of(2026, 5, 14);
int year = date.getYear(); // 2026
Month month = date.getMonth(); // MAY
int monthNumber = date.getMonthValue(); // 5
int dayOfMonth = date.getDayOfMonth(); // 14
DayOfWeek weekday = date.getDayOfWeek(); // THURSDAY
int dayOfYear = date.getDayOfYear(); // 134
int daysInMonth = date.lengthOfMonth(); // 31
boolean leap = date.isLeapYear(); // false
For a weekend check, we compare getDayOfWeek() with SATURDAY and SUNDAY, as shown in checking for a weekend. The leap year rules are built into isLeapYear().
7. Comparing Dates and Counting the Days Between Them
The isBefore(), isAfter() and isEqual() methods compare two dates on the calendar, and compareTo() orders them for sorting. To measure a gap, ChronoUnit.DAYS.between() returns a total number of days, whereas Period.between() returns years, months and days separately.
LocalDate checkIn = LocalDate.of(2026, 5, 14);
LocalDate checkOut = LocalDate.of(2026, 6, 2);
boolean valid = checkOut.isAfter(checkIn); // true
boolean sameDay = checkIn.isEqual(LocalDate.parse("2026-05-14")); // true
long nights = ChronoUnit.DAYS.between(checkIn, checkOut); // 19
Period period = Period.between(checkIn, checkOut); // P19D
long dayCount = checkIn.datesUntil(checkOut).count(); // 19
The datesUntil() method, added in Java 9, returns a Stream of every date from the start up to, but not including, the end date. The comparison methods in detail are in comparing two LocalDate instances, and other ways to count are in number of days between two dates.
8. Calculating Gym Membership Renewal Dates
Say a gym app bills members on the same day every month, starting from the day they joined. A member who joins on January 31 must pay on February 28, March 31 and April 30, because each month has a different length.
The tempting code adds one month to the previous due date. That loses days permanently, because the clamped February 28 becomes the base for every later month. Adding n months to the original join date gives the right answer every time.
static List<LocalDate> renewalDates(LocalDate joined, int months) {
List<LocalDate> dates = new ArrayList<>();
for (int n = 1; n <= months; n++) {
dates.add(joined.plusMonths(n)); // always from the join date
}
return dates;
}
LocalDate joined = LocalDate.of(2026, 1, 31);
List<LocalDate> correct = renewalDates(joined, 3); // [2026-02-28, 2026-03-31, 2026-04-30]
LocalDate drifted = joined.plusMonths(1).plusMonths(1); // 2026-03-28, a day of the month is lost
boolean dueToday = correct.contains(LocalDate.of(2026, 3, 31)); // true
9. Converting LocalDate to Other Types
Since a LocalDate has no time, converting it to a date-time type needs a time, and converting it to a moment needs a time zone as well. The atStartOfDay() and atTime() methods supply the time, and atStartOfDay(ZoneId) supplies both.
LocalDate date = LocalDate.of(2026, 5, 14);
LocalDateTime midnight = date.atStartOfDay(); // 2026-05-14T00:00
LocalDateTime lunch = date.atTime(12, 30); // 2026-05-14T12:30
ZonedDateTime parisStart = date.atStartOfDay(ZoneId.of("Europe/Paris")); // 2026-05-14T00:00+02:00[Europe/Paris]
LocalDate back = lunch.toLocalDate(); // 2026-05-14
For JDBC and older APIs, java.sql.Date.valueOf() and toLocalDate() convert in both directions. A java.util.Date is a moment, so the conversion goes through an Instant and a zone.
LocalDate date = LocalDate.of(2026, 5, 14);
java.sql.Date sqlDate = java.sql.Date.valueOf(date); // 2026-05-14
LocalDate fromSql = sqlDate.toLocalDate(); // 2026-05-14
Date legacy = Date.from(date.atStartOfDay(ZoneId.of("UTC")).toInstant());
LocalDate fromLegacy = LocalDate.ofInstant(legacy.toInstant(), ZoneId.of("UTC")); // 2026-05-14
Each conversion has its own article with more cases, namely LocalDate and LocalDateTime, LocalDate and ZonedDateTime, java.sql.Date and java.util.Date.
10. Java LocalDate FAQs
Developers who move from Date and Calendar to LocalDate search for these answers.
10.1. Why Use LocalDate Instead of java.util.Date?
A java.util.Date is a moment in time measured in milliseconds since 1970-01-01T00:00Z, so it shows a different calendar day in different zones. A LocalDate is only a calendar day. Date is also mutable and its month numbers start at 0, whereas LocalDate is immutable and January is 1.
10.2. How Do We Get the Current Date in a Specific Time Zone?
We pass the zone to now(), for example LocalDate.now(ZoneId.of(“America/New_York”)). Without the argument, the method uses the JVM default zone, which on a server is often UTC.
10.3. Is LocalDate Thread-Safe?
Yes. The class is immutable, so no method can change an existing object, and threads can share instances without synchronization. DateTimeFormatter is thread-safe for the same reason, unlike SimpleDateFormat.
10.4. Which SQL Column Type Fits a LocalDate?
We map it to a SQL DATE column. Since JDBC 4.2, PreparedStatement.setObject(index, localDate) and ResultSet.getObject(column, LocalDate.class) work without any conversion, and JPA and Hibernate map LocalDate fields to DATE by default.
10.5. Why Does LocalDate Print in yyyy-MM-dd Format?
The toString() method always uses the ISO-8601 format uuuu-MM-dd, so the output is the same on every machine. For any other display format, we call format() with a DateTimeFormatter.
11. Conclusion
A LocalDate holds a year, a month and a day with no time and no zone. Most code builds it with of() or parse(), passes a zone or a Clock to now(), and catches DateTimeParseException for user input.
Date arithmetic returns new objects and clamps the day at the end of a short month, so recurring dates are calculated from the original date. The comparison methods, ChronoUnit.DAYS.between() and the conversion methods cover the rest of the daily work. The other java.time classes are covered in the Java date and time tutorial.
12. References
- LocalDate (Java SE 25 API)
- DateTimeFormatter (Java SE 25 API)
- TemporalAdjusters (Java SE 25 API)
- The Java Tutorials, Date Classes
Happy Learning !!