Date & Time ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-18

Java LocalDate Class: Methods and Examples

The line date.plusDays(1); compiles and runs without a single warning, yet date is exactly the same afterwards. LocalDate is immutable: plusDays() returns a new object, and unless you assign it (date = date.plusDays(1);), the result is silently thrown away.

LocalDate in Java (java.time.LocalDate) is an immutable, thread-safe class that stores a date without a time and without a time zone: a year, a month and a day of the month, such as 2024-03-15. It was introduced in Java 8 as part of the java.time API and replaces java.util.Date and Calendar whenever you only need a calendar date: a birthday, a payment due date, a document expiry date.

How to create a LocalDate: of(), now(), parse()

A LocalDate object is created only through static factory methods — the class has no public constructor. LocalDate.of(year, month, day) builds a specific date, LocalDate.now() returns today's date, and LocalDate.parse("2024-03-15") reads a string in ISO-8601 format (yyyy-MM-dd).

Here is a basic Java LocalDate example that shows all the common ways to create a date:

import java.time.LocalDate;
import java.time.Month;

public class CreateLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1);
        LocalDate newYear2002 = LocalDate.of(2002, Month.JANUARY, 1);
        LocalDate fromString = LocalDate.parse("2024-03-15");
        LocalDate day32 = LocalDate.ofYearDay(2001, 32);   // 32nd day of the year
        LocalDate epoch = LocalDate.ofEpochDay(0);         // 0 days after 1970-01-01
        LocalDate today = LocalDate.now();

        System.out.println(newYear2001);
        System.out.println(newYear2002);
        System.out.println(fromString);
        System.out.println(day32);
        System.out.println(epoch);
        System.out.println(today);
    }
}
2001-01-01
2002-01-01
2024-03-15
2001-02-01
1970-01-01
2026-10-18   (the current date at the time of the run)

LocalDate.now() uses the JVM's default time zone. If you need to know what day it is in a particular city, pass a zone: LocalDate.now(ZoneId.of("Asia/Tokyo")). Tokyo is 13–14 hours ahead of New York, so for part of every day the two calls return different dates.

LocalDate.of validates the date immediately. An impossible date does not "roll over" into the next month as it did with the old lenient Calendar — it throws an exception:

LocalDate.of(2023, 2, 29);   // DateTimeException: Invalid date 'February 29' as '2023' is not a leap year
LocalDate.of(2023, 4, 31);   // DateTimeException: Invalid date 'APRIL 31'
LocalDate.of(2023, 13, 1);   // DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 13
LocalDate.parse("2023-1-5"); // DateTimeParseException: Text '2023-1-5' could not be parsed at index 5

Months start at 1

In LocalDate, January is 1 and December is 12. In java.util.Calendar January was 0, so writing LocalDate.of(2024, 0, 1) out of old habit throws a DateTimeException. To avoid the confusion altogether, use the Month enum: LocalDate.of(2024, Month.JANUARY, 1).

Getting the year, month and day

To read the parts of a LocalDate, use its getters: getYear(), getMonthValue() (a number from 1 to 12), getMonth() (the Month enum), getDayOfMonth(), getDayOfYear() and getDayOfWeek() (the DayOfWeek enum). The length of the month and year and the leap-year flag come from lengthOfMonth(), lengthOfYear() and isLeapYear().

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;

public class LocalDateGetExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2024, 3, 15);

        int year = date.getYear();
        int month = date.getMonthValue();
        Month monthAsEnum = date.getMonth();          // as an enum
        int dayYear = date.getDayOfYear();
        int dayMonth = date.getDayOfMonth();
        DayOfWeek dayWeekEnum = date.getDayOfWeek();  // as an enum

        System.out.println("Year: " + year);
        System.out.println("Month: " + month);
        System.out.println("Month name: " + monthAsEnum);
        System.out.println("Day of year: " + dayYear);
        System.out.println("Day of month: " + dayMonth);
        System.out.println("Day of week: " + dayWeekEnum);
        System.out.println("Days in month: " + date.lengthOfMonth());
        System.out.println("Days in year: " + date.lengthOfYear());
        System.out.println("Leap year: " + date.isLeapYear());
    }
}
Year: 2024
Month: 3
Month name: MARCH
Day of year: 75
Day of month: 15
Day of week: FRIDAY
Days in month: 31
Days in year: 366
Leap year: true

The generic get(TemporalField) method with ChronoField constants returns the same values. It is handy when the field is chosen at runtime:

import java.time.LocalDate;
import java.time.temporal.ChronoField;

public class LocalDateChronoFieldExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2024, 3, 15);

        System.out.println("Year: " + date.get(ChronoField.YEAR));
        System.out.println("Month: " + date.get(ChronoField.MONTH_OF_YEAR));
        System.out.println("Day of year: " + date.get(ChronoField.DAY_OF_YEAR));
        System.out.println("Day of month: " + date.get(ChronoField.DAY_OF_MONTH));
        System.out.println("Day of week: " + date.get(ChronoField.DAY_OF_WEEK)); // Monday = 1
        System.out.println("Days since 1970-01-01: " + date.getLong(ChronoField.EPOCH_DAY));
    }
}
Year: 2024
Month: 3
Day of year: 75
Day of month: 15
Day of week: 5
Days since 1970-01-01: 19797

Note the last line: EPOCH_DAY must be read with getLong(). Calling date.get(ChronoField.EPOCH_DAY) throws UnsupportedTemporalTypeException: Invalid field 'EpochDay' for get() method, use getLong() instead, because the value may not fit into an int. Time fields such as ChronoField.HOUR_OF_DAY do not exist in a LocalDate at all — asking for them also ends with UnsupportedTemporalTypeException.

Comparing dates

Two LocalDate values are compared with isBefore(), isAfter() and isEqual(), which return a boolean. For sorting, use compareTo(): the class implements Comparable. The == operator is wrong for dates — it compares references, not values.

import java.time.LocalDate;
import java.time.Month;

public class CompareLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1);
        LocalDate newYear2002 = LocalDate.of(2002, Month.JANUARY, 1);

        System.out.println("isAfter: " + newYear2001.isAfter(newYear2002));
        System.out.println("isBefore: " + newYear2001.isBefore(newYear2002));
        System.out.println("isEqual: " + newYear2001.isEqual(newYear2002));
        System.out.println("equals: " + newYear2001.equals(LocalDate.parse("2001-01-01")));
        System.out.println("compareTo: " + newYear2001.compareTo(newYear2002));
        System.out.println("compareTo: " + newYear2001.compareTo(LocalDate.of(2005, 6, 1)));
    }
}
isAfter: false
isBefore: true
isEqual: false
equals: true
compareTo: -1
compareTo: -4

The last line is easy to get wrong: LocalDate.compareTo() returns a difference (years first, then months, then days), not strictly -1, 0 or 1. Always compare the result with zero (< 0, > 0), never with -1. For two LocalDate objects, equals() and isEqual() give the same answer; they differ only when dates from different calendar systems are involved (for example, JapaneseDate) — there isEqual() compares the actual point on the timeline.

Changing a date with with()

The with() methods return a copy of the date with one field replaced: withYear(), withMonth(), withDayOfMonth(), withDayOfYear() or the generic with(ChronoField, value). The original object stays unchanged. For "smart" adjustments such as the last day of the month or the next Monday, use with(TemporalAdjuster).

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjusters;

public class WithLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1); // a Monday

        LocalDate newYear2003 = newYear2001.with(ChronoField.YEAR, 2003);
        LocalDate newYear2004 = newYear2001.withYear(2004);
        LocalDate december2001 = newYear2001.withMonth(12);
        LocalDate february2001 = newYear2001.withDayOfYear(32);
        LocalDate xmas2001 = newYear2001.withMonth(12).withDayOfMonth(25);

        LocalDate lastDay = newYear2001.with(TemporalAdjusters.lastDayOfMonth());
        LocalDate nextMonday = newYear2001.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
        LocalDate firstOfNext = newYear2001.with(TemporalAdjusters.firstDayOfNextMonth());

        LocalDate feb = LocalDate.of(2001, 1, 31).withMonth(2);

        System.out.println("newYear2003: " + newYear2003);
        System.out.println("newYear2004: " + newYear2004);
        System.out.println("december2001: " + december2001);
        System.out.println("february2001: " + february2001);
        System.out.println("xmas2001: " + xmas2001);
        System.out.println("lastDay: " + lastDay);
        System.out.println("nextMonday: " + nextMonday);
        System.out.println("firstOfNext: " + firstOfNext);
        System.out.println("feb: " + feb);
    }
}
newYear2003: 2003-01-01
newYear2004: 2004-01-01
december2001: 2001-12-01
february2001: 2001-02-01
xmas2001: 2001-12-25
lastDay: 2001-01-31
nextMonday: 2001-01-08
firstOfNext: 2001-02-01
feb: 2001-02-28

Two details in this output deserve attention. TemporalAdjusters.next(MONDAY) looks for a Monday strictly after the current date, so starting from Monday, January 1 it returns January 8; if the current day itself should count, use nextOrSame(). And withMonth(2) applied to January 31 does not fail — it clamps the day to the last valid one, February 28. By contrast, withDayOfMonth(30) on a February date throws DateTimeException: Invalid date 'FEBRUARY 30', because here you explicitly ask for a day that does not exist.

plus() and minus() methods

To add or subtract days, weeks, months or years, use plusDays(), plusWeeks(), plusMonths(), plusYears(), their minus...() counterparts, or the generic plus(amount, ChronoUnit) and minus(amount, ChronoUnit). Every call returns a new date and leaves the original untouched.

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class PlusMinusLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1);

        // Adding
        LocalDate newYear2005 = newYear2001.plusYears(4);
        LocalDate march2001 = newYear2001.plusMonths(2);
        LocalDate jan15Year2001 = newYear2001.plusDays(14);
        LocalDate jan22Year2001 = newYear2001.plusWeeks(3);
        LocalDate newYear2006 = newYear2001.plus(5, ChronoUnit.YEARS);

        // Subtracting
        LocalDate newYear2000 = newYear2001.minusYears(1);
        LocalDate nov2000 = newYear2001.minusMonths(2);
        LocalDate dec30Year2000 = newYear2001.minusDays(2);
        LocalDate dec25Year2000 = newYear2001.minusWeeks(1);
        LocalDate newYear1999 = newYear2001.minus(2, ChronoUnit.YEARS);

        System.out.println("newYear2005: " + newYear2005);
        System.out.println("march2001: " + march2001);
        System.out.println("jan15Year2001: " + jan15Year2001);
        System.out.println("jan22Year2001: " + jan22Year2001);
        System.out.println("newYear2006: " + newYear2006);

        System.out.println("newYear2000: " + newYear2000);
        System.out.println("nov2000: " + nov2000);
        System.out.println("dec30Year2000: " + dec30Year2000);
        System.out.println("dec25Year2000: " + dec25Year2000);
        System.out.println("newYear1999: " + newYear1999);
    }
}
newYear2005: 2005-01-01
march2001: 2001-03-01
jan15Year2001: 2001-01-15
jan22Year2001: 2001-01-22
newYear2006: 2006-01-01
newYear2000: 2000-01-01
nov2000: 2000-11-01
dec30Year2000: 2000-12-30
dec25Year2000: 2000-12-25
newYear1999: 1999-01-01

What does this code print? The end of the month is where date arithmetic stops behaving the way most people expect:

LocalDate jan31 = LocalDate.of(2001, 1, 31);

System.out.println(jan31.plusMonths(1));               // 2001-02-28
System.out.println(jan31.plusMonths(1).plusMonths(1)); // 2001-03-28
System.out.println(jan31.plusMonths(2));               // 2001-03-31

After the first plusMonths(1) the day is clamped to 28, and the second call has no way of knowing that the original day was the 31st. If you generate recurring dates such as monthly payment days, always add N months to the original start date instead of adding one month to the previous result.

LocalDate does not support time units: there is no plusHours() method, and date.plus(1, ChronoUnit.HOURS) throws UnsupportedTemporalTypeException: Unsupported unit: Hours. For a date with a time of day, use LocalDateTime. To add "1 year, 2 months and 3 days" in one go, pass a Period: date.plus(Period.of(1, 2, 3)).

How many days between two dates

The number of days between two LocalDate values is returned by ChronoUnit.DAYS.between(start, end) or the equivalent start.until(end, ChronoUnit.DAYS). The result is a long, the end date is exclusive, and if the end is before the start, the number is negative.

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class DaysBetweenExample {
    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2001, 1, 1);
        LocalDate end = LocalDate.of(2001, 3, 1);

        System.out.println(ChronoUnit.DAYS.between(start, end));   // 59
        System.out.println(start.until(end, ChronoUnit.DAYS));     // 59
        System.out.println(ChronoUnit.MONTHS.between(start, end)); // 2
        System.out.println(ChronoUnit.DAYS.between(end, start));   // -59
    }
}

If you need the difference as "N years, M months, K days", use Period.between() — the Period class is a separate topic of this module.

Parsing and formatting a string

LocalDate.toString() and LocalDate.parse(String) always use the ISO-8601 format yyyy-MM-dd. For any other format, such as 03/15/2024 or 15.03.2024, you need a DateTimeFormatter, passed to parse(text, formatter) and to format(formatter).

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class LocalDateFormatExample {
    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");

        LocalDate date = LocalDate.parse("03/15/2024", formatter);
        System.out.println(date);                   // 2024-03-15
        System.out.println(date.format(formatter)); // 03/15/2024
    }
}

Pattern letters, locales and parsing month names belong to the DateTimeFormatter lesson of this module.

Converting LocalDate to java.util.Date and java.sql.Date

There is no direct LocalDate to Date conversion: a LocalDate has no time and no zone, while java.util.Date is an instant on the timeline. So you first choose a zone and a time of day: Date.from(localDate.atStartOfDay(zone).toInstant()). The reverse is date.toInstant().atZone(zone).toLocalDate(). With java.sql.Date it is simpler — there are ready-made java.sql.Date.valueOf(localDate) and sqlDate.toLocalDate().

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;

public class LocalDateConvertExample {
    public static void main(String[] args) {
        ZoneId zone = ZoneId.systemDefault();
        LocalDate localDate = LocalDate.of(2024, 3, 15);

        // LocalDate -> java.util.Date (midnight in the given zone)
        Date utilDate = Date.from(localDate.atStartOfDay(zone).toInstant());

        // java.util.Date -> LocalDate
        LocalDate back = utilDate.toInstant().atZone(zone).toLocalDate();
        LocalDate back9 = LocalDate.ofInstant(utilDate.toInstant(), zone); // Java 9+

        // LocalDate <-> java.sql.Date
        java.sql.Date sqlDate = java.sql.Date.valueOf(localDate);
        LocalDate fromSql = sqlDate.toLocalDate();

        System.out.println(utilDate);
        System.out.println(back);
        System.out.println(back9);
        System.out.println(sqlDate);
        System.out.println(fromSql);
    }
}
Fri Mar 15 00:00:00 EDT 2024   (default zone America/New_York)
2024-03-15
2024-03-15
2024-03-15
2024-03-15
Direction Code Note
LocalDate → java.util.Date Date.from(ld.atStartOfDay(zone).toInstant()) The time becomes 00:00 in the chosen zone
java.util.Date → LocalDate d.toInstant().atZone(zone).toLocalDate() Shorter in Java 9+: LocalDate.ofInstant(d.toInstant(), zone)
LocalDate → java.sql.Date java.sql.Date.valueOf(ld) No zone needed
java.sql.Date → LocalDate sqlDate.toLocalDate() sqlDate.toInstant() throws UnsupportedOperationException
Epoch milliseconds → LocalDate Instant.ofEpochMilli(ms).atZone(zone).toLocalDate() Instant is covered in its own lesson of this module
JDBC 4.2+ rs.getObject("col", LocalDate.class), ps.setObject(1, ld) No conversion through java.sql.Date needed

Use the same zone both ways

Convert there and back with the same ZoneId. If you store midnight of March 15 in Tokyo (UTC+9) and read it back in ZoneOffset.UTC, you get March 14, 15:00 — that is, the LocalDate 2024-03-14, one day earlier.

LocalDate methods cheat sheet

The methods of LocalDate fall into five groups: creating, reading fields, comparing, getting a modified copy and converting. Methods that seem to "change" a date actually return a new object. The full list is in the official LocalDate Javadoc.

Method What it does Edge case / error
of(y, m, d) Creates a date DateTimeException for a non-existent date
now(), now(ZoneId) Today's date Without a zone, the JVM default zone is used
parse(text) Parses a yyyy-MM-dd string DateTimeParseException for "2023-1-5"
getYear(), getMonthValue(), getDayOfMonth() Year, month (1–12), day Months start at 1, not 0
getDayOfWeek() Day of the week (DayOfWeek) As a number via ChronoField: Monday = 1
lengthOfMonth(), isLeapYear() Days in the month, leap year or not February 2024 has 29 days, February 2023 has 28
isBefore(), isAfter(), isEqual() Comparison NullPointerException if the argument is null
withMonth(), withDayOfMonth(), with(TemporalAdjuster) Copy with a replaced field withMonth(2) from the 31st gives the 28th/29th
plusDays() … minusYears() Copy shifted by N units plus(1, HOURS) → UnsupportedTemporalTypeException
atStartOfDay(), atTime() Move to LocalDateTime atStartOfDay(zone) returns a ZonedDateTime
toEpochDay() Days since 1970-01-01 Negative for dates before 1970

Common mistakes

Most bugs with LocalDate come from three things: immutability, month numbering and the fact that a date has no time. Here are the typical slips and how to fix them.

Mistake What happens Correct version
date.plusDays(1); The result is lost, date does not change date = date.plusDays(1);
LocalDate.of(2024, 0, 1) DateTimeException: months are 1–12 LocalDate.of(2024, Month.JANUARY, 1)
d1 == d2 Compares references, usually false d1.isEqual(d2) or d1.equals(d2)
d1.compareTo(d2) == -1 May return -4, so the condition fails d1.compareTo(d2) < 0 or d1.isBefore(d2)
LocalDate.parse("03/15/2024") DateTimeParseException LocalDate.parse(s, DateTimeFormatter.ofPattern("MM/dd/yyyy"))
Monthly dates via chained plusMonths(1) The 31st drifts to the 28th for good start.plusMonths(n) from the original date

Frequently asked questions

What is the difference between LocalDate, LocalDateTime and java.util.Date?

LocalDate holds only a date (2024-03-15), LocalDateTime holds a date and a time without a zone (2024-03-15T10:30), and java.util.Date is an instant in milliseconds since 1970 that is also mutable. Use LocalDate for a birthday or a due date, and LocalDateTime or Instant for the time of an event. How to convert between them is shown in Converting LocalDate to java.util.Date.

Is LocalDate thread-safe? Can I keep it in a static final field?

Yes. LocalDate is immutable, so a single instance can be shared by any number of threads without synchronization and safely declared as a constant, for example static final LocalDate RELEASE = LocalDate.of(2014, 3, 18);. Unlike SimpleDateFormat, DateTimeFormatter is thread-safe too.

How do I sort a list of LocalDate objects?

LocalDate implements Comparable, so list.sort(Comparator.naturalOrder()) or Collections.sort(list) is enough — from the earliest date to the latest. For reverse order use list.sort(Comparator.reverseOrder()). To find the latest date, call Collections.max(list).

How do I unit-test code that calls LocalDate.now()?

Inject a Clock and call LocalDate.now(clock). In production pass Clock.systemDefaultZone(); in a test pass a fixed clock: Clock.fixed(Instant.parse("2024-03-15T10:00:00Z"), ZoneOffset.UTC). Then LocalDate.now(clock) always returns 2024-03-15.

Comments

Please log in or register to have a possibility to add comment.