Java LocalDate

In the last lesson you learned about the Java Calendar class. Java 8 replaced that clumsy tool with a fresh set of classes in java.time. The first one to meet is LocalDate, a plain calendar date like a birthday or a deadline.

πŸ€” The problem with old dates

Say you need a feature like β€œyour library book is due in 14 days”. You only care about the day, not the hour or the zone. The old tools made that harder than it should be:

  • Date always carried a time and a hidden time zone.
  • Calendar could be changed by any line that touched it, so a stored date could quietly shift.
  • Month numbering was a trap. January was 0, not 1.
// the old, error-prone way
Calendar c = Calendar.getInstance();
c.set(2026, 6, 13); // surprise: month 6 means JULY, not June

LocalDate removes the whole problem. It is a date with no time and no zone, months are numbered the human way, and once created it can never change.

🧩 What is a LocalDate?

A LocalDate is a single calendar date written as year, month, and day, like 2026-06-13. It is just the box on a wall calendar, no hour, no city.

Two rules matter from the start:

  • Months count from 1. June is 6, the way you say it out loud.
  • It is immutable. Once it exists, it never changes. Any method that looks like it edits the date actually returns a brand new LocalDate. This is the single most important fact in the lesson.

πŸ“… Creating a LocalDate

Two everyday ways to make one:

  • LocalDate.now() reads today’s date from the system clock.
  • LocalDate.of(...) builds a specific date you choose.
import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(2026, 6, 13);
System.out.println("Today is: " + today);
System.out.println("A set date: " + birthday);
}
}

Reading that top to bottom:

  • LocalDate.now() asks the clock for today.
  • LocalDate.of(2026, 6, 13) builds 13 June 2026, month written as a normal 6.
  • Printing a LocalDate gives the clean YYYY-MM-DD format for free.

Output

Today is: 2026-06-13
A set date: 2026-06-13

Tip

An impossible date like LocalDate.of(2026, 2, 30) throws a DateTimeException right away instead of rolling over to March. That early error catches bad input on the spot.

πŸ” Reading the parts of a date

Often you need a single piece of a date, like the year for a report or the weekday for a weekend check. LocalDate has a clear getter for each part.

import java.time.LocalDate;
import java.time.DayOfWeek;
import java.time.Month;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2026, 6, 13);
System.out.println("Year: " + d.getYear());
System.out.println("Month name: " + d.getMonth());
System.out.println("Month number: " + d.getMonthValue());
System.out.println("Day of month: " + d.getDayOfMonth());
System.out.println("Day of week: " + d.getDayOfWeek());
System.out.println("Day of year: " + d.getDayOfYear());
}
}

A few to note:

  • getMonth() returns a Month like JUNE; getMonthValue() returns the number 6.
  • getDayOfWeek() returns a DayOfWeek like SATURDAY, perfect for weekend checks with no math.

Output

Year: 2026
Month name: JUNE
Month number: 6
Day of month: 13
Day of week: SATURDAY
Day of year: 164

βž• It is immutable: plus and minus

This is the part that trips people up:

  • plusDays, minusMonths, and plusYears do not move the original date.
  • Each one returns a fresh LocalDate you must catch in a variable.
  • Ignore the return value and nothing happens.
import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2026, 6, 13);
LocalDate inTwoWeeks = start.plusDays(14);
LocalDate lastMonth = start.minusMonths(1);
LocalDate nextYear = start.plusYears(1);
System.out.println("Start: " + start);
System.out.println("In 2 weeks: " + inTwoWeeks);
System.out.println("Last month: " + lastMonth);
System.out.println("Next year: " + nextYear);
}
}

start is still 2026-06-13 at the end, even after three method calls. Each call returned a new object and left the original alone. That is what immutable means, and it is why LocalDate is safe to pass around without fear of edits.

Output

Start: 2026-06-13
In 2 weeks: 2026-06-27
Last month: 2026-05-13
Next year: 2027-06-13

Because each call returns a LocalDate, you can chain them. This reads almost like a sentence.

LocalDate result = LocalDate.of(2026, 6, 13)
.plusYears(1)
.plusMonths(2)
.minusDays(3);
System.out.println(result); // 2027-08-10

βš–οΈ Comparing two dates

To find which date comes first, use isBefore, isAfter, and isEqual. Each returns a plain true or false, so they slot straight into an if.

import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
LocalDate today = LocalDate.of(2026, 6, 13);
LocalDate deadline = LocalDate.of(2026, 6, 30);
System.out.println(today.isBefore(deadline)); // true
System.out.println(today.isAfter(deadline)); // false
System.out.println(today.isEqual(deadline)); // false
if (today.isAfter(deadline)) {
System.out.println("The deadline has passed.");
} else {
System.out.println("You still have time.");
}
}
}

The names say exactly what they do. today.isAfter(deadline) asks β€œis today later than the deadline?”. Here it is not, so we print the friendly message.

Output

true
false
false
You still have time.

πŸ—“οΈ Handy checks: leap years and month length

LocalDate answers two questions that used to need awkward math:

  • isLeapYear() tells you if the year is a leap year.
  • lengthOfMonth() and lengthOfYear() give the days in the month and year.
import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2024, 2, 1);
System.out.println("Is 2024 a leap year? " + d.isLeapYear());
System.out.println("Days in this month: " + d.lengthOfMonth());
System.out.println("Days in this year: " + d.lengthOfYear());
LocalDate normal = LocalDate.of(2026, 2, 1);
System.out.println("Days in Feb 2026: " + normal.lengthOfMonth());
}
}

Because 2024 is a leap year, February has 29 days and the year has 366. For 2026, a normal year, February drops back to 28. The class knows the rules so you never write them by hand.

Output

Is 2024 a leap year? true
Days in this month: 29
Days in this year: 366
Days in Feb 2026: 28

πŸ”’ Counting days between two dates

A common need is β€œhow many days from here to there?”. Two clean ways:

  • ChronoUnit.DAYS.between gives a plain count of days.
  • Period.between breaks the gap into years, months, and days.
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
LocalDate today = LocalDate.of(2026, 6, 13);
LocalDate newYear = LocalDate.of(2027, 1, 1);
long days = ChronoUnit.DAYS.between(today, newYear);
System.out.println("Days until New Year: " + days);
Period gap = Period.between(today, newYear);
System.out.println("That is " + gap.getMonths() + " months and "
+ gap.getDays() + " days.");
}
}

The difference in one line:

  • ChronoUnit.DAYS.between returns a single long, the total day count.
  • Period.between returns a Period you can ask for months and days separately.
  • Use the first for one number, the second for a human-friendly breakdown.

Output

Days until New Year: 202
That is 6 months and 19 days.

Let’s put this to work with a tiny β€œdays until your birthday” calculator. Take this year’s birthday, roll it to next year if it already passed, then count.

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
LocalDate today = LocalDate.of(2026, 6, 13);
// Alex was born on 20 September
LocalDate birthday = LocalDate.of(today.getYear(), 9, 20);
if (birthday.isBefore(today)) {
birthday = birthday.plusYears(1); // already happened, so use next year
}
long daysToGo = ChronoUnit.DAYS.between(today, birthday);
System.out.println("Days until the next birthday: " + daysToGo);
}
}

Notice the immutability rule again. birthday.plusYears(1) does not change the old date, so we catch the new one back into the same variable. Forget the birthday = part and the count would be wrong.

Output

Days until the next birthday: 99

🎨 Formatting a date as text

Printing a LocalDate gives 2026-06-13. A person might prefer β€œ13 June 2026”. To control the look, build a DateTimeFormatter with a pattern, then call format.

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2026, 6, 13);
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("d MMMM yyyy");
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("EEE, MMM d");
System.out.println(d.format(f1));
System.out.println(d.format(f2));
System.out.println(d.format(f3));
}
}

Each pattern letter stands for a part of the date:

  • dd is the day with a leading zero; MM is the month number; yyyy is the four-digit year.
  • MMMM is the full month name; EEE is the short weekday name.
  • Mix and match the letters to get any layout you need.

Output

13/06/2026
13 June 2026
Sat, Jun 13

πŸ“₯ Parsing text back into a date

The reverse job is just as common. A user types a date, or you read one from a file, and you need a real LocalDate. Use LocalDate.parse.

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
// default: parse the standard YYYY-MM-DD form
LocalDate a = LocalDate.parse("2026-06-13");
System.out.println(a.plusDays(1)); // 2026-06-14
// with a custom pattern for other layouts
DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate b = LocalDate.parse("13/06/2026", f);
System.out.println(b.getMonth()); // JUNE
}
}

How parse works:

  • The one-argument form expects the standard YYYY-MM-DD shape.
  • For any other layout, pass a DateTimeFormatter that matches it, just like for formatting.
  • Once parsed, it is a normal LocalDate you can do math on.

Output

2026-06-14
JUNE

Caution

If the text does not match the pattern, parse throws a DateTimeParseException. When the input comes from a user, wrap the call in a try-catch so a typo does not crash your program.

⚠️ Common Mistakes

A few LocalDate slip-ups to watch for.

  • Ignoring the returned value. This is the big one. plusDays returns a new date; do not catch the result and your code does nothing.
// ❌ the result is thrown away; date never moves
LocalDate date = LocalDate.of(2026, 6, 13);
date.plusDays(10);
System.out.println(date); // still 2026-06-13
// βœ… catch the new date that plusDays hands back
LocalDate date2 = LocalDate.of(2026, 6, 13);
date2 = date2.plusDays(10);
System.out.println(date2); // 2026-06-23
  • Carrying over the old off-by-one habit. In Calendar, June was 5. In LocalDate, June is 6. Use the real human number.
// ❌ writing month the old zero-based way: this is MAY, not June
LocalDate wrong = LocalDate.of(2026, 5, 13);
// βœ… months count from 1, so June is 6
LocalDate right = LocalDate.of(2026, 6, 13);
  • Mixing up name and number. getMonth() returns JUNE; getMonthValue() returns 6. Reach for the one you need.

  • Forgetting parse is strict. "2026-6-13" (no leading zero) does not match the default pattern and throws. Zero-pad the input or supply a matching formatter.

βœ… Best Practices

Habits for using LocalDate well.

  • Use LocalDate when there is no time. Birthdays, deadlines, holidays. If the hour does not matter, do not drag a time and zone along.
  • Always assign the result of plus/minus. Write date = date.plusDays(7), never a bare date.plusDays(7).
  • Let the class do the math. Use isLeapYear, lengthOfMonth, and ChronoUnit.DAYS.between instead of hand-rolled formulas.
  • Reuse formatters. A DateTimeFormatter is immutable and thread-safe, so create it once (a static final constant) and reuse it.
  • Guard parse against bad input. Wrap it in a try-catch for DateTimeParseException when text comes from a user or file.

🧩 What You’ve Learned

Nicely done. Quick recap.

  • βœ… A LocalDate is a date with no time and no zone, just year, month, and day, with months counted from 1.
  • βœ… Create one with LocalDate.now() for today or LocalDate.of(2026, 6, 13) for a chosen date, and read parts with getYear, getMonthValue, getDayOfMonth, and getDayOfWeek.
  • βœ… LocalDate is immutable, so plusDays, minusMonths, and plusYears return a new date you must assign.
  • βœ… Compare with isBefore, isAfter, isEqual, and use isLeapYear and lengthOfMonth for calendar facts.
  • βœ… Count gaps with ChronoUnit.DAYS.between or Period.between, and convert to and from text with DateTimeFormatter and LocalDate.parse.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does a LocalDate hold?

    Why: LocalDate is purely a calendar date: year, month, and day, with no time and no zone.

  2. 2

    In LocalDate.of(2026, 6, 13), which month is 6?

    Why: LocalDate counts months from 1, so 6 is June. There is no zero-based off-by-one like Calendar.

  3. 3

    What does date.plusDays(10) do to the original date object?

    Why: LocalDate is immutable. plusDays returns a new date, so you must assign the result.

  4. 4

    Which gives the plain number of days between two dates?

    Why: ChronoUnit.DAYS.between(a, b) returns the total day count as a long. Period.between splits it into years, months, and days.

πŸš€ What’s Next?

You can now work with any calendar date. Sometimes you care about the clock instead, the hours and minutes with no date attached. That is the next piece of java.time. Up next: Java LocalTime.

Share & Connect