Java LocalDate
Table of Contents + β
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:
Datealways carried a time and a hidden time zone.Calendarcould be changed by any line that touched it, so a stored date could quietly shift.- Month numbering was a trap. January was
0, not1.
// the old, error-prone wayCalendar c = Calendar.getInstance();c.set(2026, 6, 13); // surprise: month 6 means JULY, not JuneLocalDate 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 normal6.- Printing a LocalDate gives the clean
YYYY-MM-DDformat for free.
Output
Today is: 2026-06-13A set date: 2026-06-13Tip
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 aMonthlikeJUNE;getMonthValue()returns the number6.getDayOfWeek()returns aDayOfWeeklikeSATURDAY, perfect for weekend checks with no math.
Output
Year: 2026Month name: JUNEMonth number: 6Day of month: 13Day of week: SATURDAYDay of year: 164β It is immutable: plus and minus
This is the part that trips people up:
plusDays,minusMonths, andplusYearsdo 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-13In 2 weeks: 2026-06-27Last month: 2026-05-13Next year: 2027-06-13Because 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
truefalsefalseYou 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()andlengthOfYear()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? trueDays in this month: 29Days in this year: 366Days 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.betweengives a plain count of days.Period.betweenbreaks 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.betweenreturns a singlelong, the total day count.Period.betweenreturns aPeriodyou 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: 202That 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:
ddis the day with a leading zero;MMis the month number;yyyyis the four-digit year.MMMMis the full month name;EEEis the short weekday name.- Mix and match the letters to get any layout you need.
Output
13/06/202613 June 2026Sat, 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-DDshape. - For any other layout, pass a
DateTimeFormatterthat matches it, just like for formatting. - Once parsed, it is a normal LocalDate you can do math on.
Output
2026-06-14JUNECaution
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.
plusDaysreturns a new date; do not catch the result and your code does nothing.
// β the result is thrown away; date never movesLocalDate date = LocalDate.of(2026, 6, 13);date.plusDays(10);System.out.println(date); // still 2026-06-13
// β
catch the new date that plusDays hands backLocalDate 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 was5. In LocalDate, June is6. Use the real human number.
// β writing month the old zero-based way: this is MAY, not JuneLocalDate wrong = LocalDate.of(2026, 5, 13);
// β
months count from 1, so June is 6LocalDate right = LocalDate.of(2026, 6, 13);-
Mixing up name and number.
getMonth()returnsJUNE;getMonthValue()returns6. Reach for the one you need. -
Forgetting
parseis 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 baredate.plusDays(7). - Let the class do the math. Use
isLeapYear,lengthOfMonth, andChronoUnit.DAYS.betweeninstead of hand-rolled formulas. - Reuse formatters. A
DateTimeFormatteris immutable and thread-safe, so create it once (astatic finalconstant) and reuse it. - Guard
parseagainst bad input. Wrap it in atry-catchforDateTimeParseExceptionwhen 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 orLocalDate.of(2026, 6, 13)for a chosen date, and read parts withgetYear,getMonthValue,getDayOfMonth, andgetDayOfWeek. - β
LocalDate is immutable, so
plusDays,minusMonths, andplusYearsreturn a new date you must assign. - β
Compare with
isBefore,isAfter,isEqual, and useisLeapYearandlengthOfMonthfor calendar facts. - β
Count gaps with
ChronoUnit.DAYS.betweenorPeriod.between, and convert to and from text withDateTimeFormatterandLocalDate.parse.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does a LocalDate hold?
Why: LocalDate is purely a calendar date: year, month, and day, with no time and no zone.
- 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
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
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.