To get all dates between two dates in Java, we call startDate.datesUntil(endDate) and collect the stream into a list. The method datesUntil() was added to LocalDate in Java 9, and it returns one LocalDate for each day.
We need the list of dates when we fill a booking calendar or build one report row per day of a month.
The following example lists the dates from Oct 1 to Oct 5, 2026, once without and once with the end date.
LocalDate start = LocalDate.of(2026, 10, 1);
LocalDate end = LocalDate.of(2026, 10, 5);
List<LocalDate> dates = start.datesUntil(end).toList();
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04]
List<LocalDate> withEnd = start.datesUntil(end.plusDays(1)).toList();
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04, 2026-10-05]
Notice that the list starts with the start date and stops one day before the end date, so to include the end date too, we pass end.plusDays(1).
Next, we step by a week or a month and list dates in reverse order. After that, we look at the Java 8 way, counting days, business days, LocalDateTime ranges and the old Date and Calendar classes.
1. What datesUntil() Returns
The method datesUntil() returns a Stream<LocalDate>, which is a sequence of values that we can filter, count or collect.
The toList() call (Java 16+) collects the stream into an unmodifiable List, whereas in Java 8 to 15 we convert the stream to a list with collect(Collectors.toList()).
The start date is inclusive and the end date is exclusive. Inclusive means the date is in the result, and exclusive means the result stops one day before that date. String.substring() and IntStream.range() follow the same rule.

When the start date and the end date are equal, datesUntil() returns an empty stream. A range across February 29 of a leap year needs no extra code.
LocalDate start = LocalDate.of(2026, 10, 1);
List<LocalDate> none = start.datesUntil(start).toList(); // [] (start equals end)
List<LocalDate> one = start.datesUntil(start.plusDays(1)).toList(); // [2026-10-01]
Stream<LocalDate> bad = start.datesUntil(LocalDate.of(2026, 9, 28)); // IllegalArgumentException: 2026-09-28 < 2026-10-01
List<LocalDate> leap = LocalDate.of(2028, 2, 27).datesUntil(LocalDate.of(2028, 3, 2)).toList();
// [2028-02-27, 2028-02-28, 2028-02-29, 2028-03-01] (leap year)
long days = LocalDate.of(2026, 1, 1).datesUntil(LocalDate.of(2027, 1, 1)).count(); // 365
When the end date is before the start date, datesUntil() throws IllegalArgumentException. So when the dates come from user input, we check the order first. Month lengths and leap years need no extra code, because LocalDate calculates the correct number of days in each month.
LocalDate from = LocalDate.of(2026, 10, 5);
LocalDate to = LocalDate.of(2026, 10, 1);
List<LocalDate> safe = from.isAfter(to) ? List.of() : from.datesUntil(to).toList(); // []
1.1. The Two datesUntil() Signatures
The class LocalDate has two versions of datesUntil(), both added in Java 9 and unchanged in Java 25. Both return a sequential, ordered stream, and both reject a range that can never reach the end date.
| Signature | Step between dates | Throws IllegalArgumentException when |
|---|---|---|
| Stream<LocalDate> datesUntil(LocalDate endExclusive) | 1 day | The end date is before the start date |
| Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step) | The given Period | The step is zero, the step moves away from the end date, or its months and days have opposite signs |
A null end date throws a NullPointerException in both versions. The opposite-sign rule catches a step such as Period.of(0, 1, -1) (one month minus one day), because datesUntil() cannot tell which direction such a step goes.
LocalDate from = LocalDate.of(2026, 10, 1);
Stream<LocalDate> noEnd = from.datesUntil(null); // NullPointerException
Stream<LocalDate> mixed = from.datesUntil(LocalDate.of(2027, 1, 1), Period.of(0, 1, -1)); // IllegalArgumentException: period months and days are of opposite sign
The returned stream is lazy, so datesUntil() creates each LocalDate only when the next stream operation asks for it. For example, a reminder app that warns users about the next Friday the 13th can search ten years of dates, and the stream stops at the first match without creating the other dates.
LocalDate from = LocalDate.of(2026, 10, 1);
LocalDate until = LocalDate.of(2036, 1, 1);
Predicate<LocalDate> isFriday13 = d -> d.getDayOfMonth() == 13 && d.getDayOfWeek() == DayOfWeek.FRIDAY;
Optional<LocalDate> next = from.datesUntil(until).filter(isFriday13).findFirst(); // Optional[2026-11-13]
Like every Java stream, the result of datesUntil() can be used only once. A second terminal operation on the same stream throws an IllegalStateException, so when we need the dates twice, we either keep the List or call datesUntil() again.
LocalDate from = LocalDate.of(2026, 10, 1);
Stream<LocalDate> week = from.datesUntil(from.plusDays(7));
long count = week.count(); // 7
List<LocalDate> again = week.toList(); // IllegalStateException: stream has already been operated upon or closed
List<LocalDate> fresh = from.datesUntil(from.plusDays(7)).toList(); // 7 dates, 2026-10-01 to 2026-10-07
2. Including the End Date
Most real ranges include both dates. For example, a hotel booking “from Oct 1 to Oct 5” or a report “for October” includes the last day. For an inclusive range, we pass end.plusDays(1) as the end.
List<LocalDate> withEnd = start.datesUntil(end.plusDays(1)).toList();
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04, 2026-10-05]
A whole month is a common case. YearMonth holds only a year and a month, and atDay(1) gives the first day of the month, so the first day of the next month is the exclusive end.
YearMonth month = YearMonth.of(2026, 2);
List<LocalDate> feb = month.atDay(1)
.datesUntil(month.plusMonths(1).atDay(1))
.toList();
// 28 dates, from 2026-02-01 to 2026-02-28
3. Step by a Week or a Month
The second version of the method, datesUntil(endExclusive, Period step), takes a step. A Period is an amount of time in days, months and years, such as “1 week” or “1 month”, and the stream moves forward by that amount instead of one day. For example, a fitness app that books a class every Thursday steps by one week from the first class date.
LocalDate start = LocalDate.of(2026, 10, 1);
LocalDate end = LocalDate.of(2026, 11, 1);
List<LocalDate> weekly = start.datesUntil(end, Period.ofWeeks(1)).toList();
// [2026-10-01, 2026-10-08, 2026-10-15, 2026-10-22, 2026-10-29]
List<LocalDate> tenDays = start.datesUntil(end, Period.ofDays(10)).toList();
// [2026-10-01, 2026-10-11, 2026-10-21, 2026-10-31]
LocalDate jan31 = LocalDate.of(2026, 1, 31);
List<LocalDate> monthly = jan31.datesUntil(LocalDate.of(2026, 6, 1), Period.ofMonths(1)).toList();
// [2026-01-31, 2026-02-28, 2026-03-31, 2026-04-30, 2026-05-31]
In the monthly result, February has no 31st, so the second date is Feb 28, but the next date is still March 31, not March 28.
The reason is that datesUntil() adds the step to the start date each time (start + 1 month, start + 2 months, and so on). A loop that adds one month to the previous date gives a different answer.
List<LocalDate> drift = Stream.iterate(jan31, d -> d.plusMonths(1)).limit(5).toList();
// [2026-01-31, 2026-02-28, 2026-03-28, 2026-04-28, 2026-05-28]
After February, every date in the loop stays on the 28th, so for “last day of each month” schedules, datesUntil() with a Period gives the correct dates.
3.1. Dates in Reverse Order
A negative step lists the dates backward. In that case, the end date must be before the start date, and the end is still exclusive.
List<LocalDate> backward = LocalDate.of(2026, 10, 5)
.datesUntil(LocalDate.of(2026, 9, 30), Period.ofDays(-1))
.toList();
// [2026-10-05, 2026-10-04, 2026-10-03, 2026-10-02, 2026-10-01]
Two kinds of step throw IllegalArgumentException. Period.ZERO fails with the message “step is zero”, and a step that moves away from the end date fails too, so a step of -1 day with an end date in the future fails with “2026-11-01 > 2026-10-01”.
4. The Java 8 Way
The method datesUntil() does not exist in Java 8, so we first count the days between the two dates with ChronoUnit.DAYS.between() and then build that many dates. Java 8 code builds the dates in one of three common ways.
- Stream.iterate() starts at the start date and adds one day each time, and limit() stops the stream after the counted number of days.
- LongStream.range(0, n) gives the numbers 0 to n – 1, and each number becomes start.plusDays(number).
- A classic for loop adds one day until it reaches the end date.
LocalDate start = LocalDate.of(2026, 10, 1);
LocalDate end = LocalDate.of(2026, 10, 5);
long numOfDays = ChronoUnit.DAYS.between(start, end); // 4
List<LocalDate> a = Stream.iterate(start, date -> date.plusDays(1))
.limit(numOfDays)
.collect(Collectors.toList());
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04]
List<LocalDate> b = LongStream.range(0, numOfDays)
.mapToObj(start::plusDays)
.collect(Collectors.toList());
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04]
List<LocalDate> c = new ArrayList<>();
for (LocalDate d = start; d.isBefore(end); d = d.plusDays(1)) {
c.add(d);
}
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04]
All three give the same list as datesUntil(end). To include the end date, we either add 1 to numOfDays or write !d.isAfter(end) in the loop.
Java 9 also added a three-argument Stream.iterate(), whose second argument is the condition to keep going, so we do not need to count the days first.
List<LocalDate> untilEnd = Stream.iterate(start, d -> d.isBefore(end), d -> d.plusDays(1)).toList();
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04]
5. Counting the Days Only
When we need only the number of days, not the dates, we do not build a list at all, because ChronoUnit.DAYS.between() returns the count. For example, a hotel app needs the number of nights to compute the price, and it never looks at the single dates.
LocalDate start = LocalDate.of(2026, 10, 1);
LocalDate end = LocalDate.of(2026, 12, 1);
long days = ChronoUnit.DAYS.between(start, end); // 61
long counted = start.datesUntil(end).count(); // 61
long inclusive = ChronoUnit.DAYS.between(start, end) + 1; // 62 (both dates counted)
long negative = ChronoUnit.DAYS.between(end, start); // -61
Period period = start.until(end); // P2M (a Period: 2 months)
The method DAYS.between() uses the same rule as datesUntil(), so the start date counts and the end date does not. A reversed pair gives a negative number instead of an exception.
The method until() returns a Period, which counts months and days separately. So we use until() for a difference between two dates in months and days, and ChronoUnit.DAYS.between() to count days between two dates.
6. Business Days Between Two Dates (Skip Weekends)
Business days are the days from Monday to Friday, and because datesUntil() returns a stream, we can drop Saturdays and Sundays with filter(). The method getDayOfWeek() returns a DayOfWeek value such as SATURDAY. For example, a leave request form counts only the working days between the first and the last day off.
LocalDate start = LocalDate.of(2026, 10, 1);
LocalDate end = LocalDate.of(2026, 10, 15);
Set<DayOfWeek> weekend = Set.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
List<LocalDate> workDays = start.datesUntil(end)
.filter(d -> !weekend.contains(d.getDayOfWeek()))
.toList();
// [2026-10-01, 2026-10-02, 2026-10-05, 2026-10-06, 2026-10-07,
// 2026-10-08, 2026-10-09, 2026-10-12, 2026-10-13, 2026-10-14]
int size = workDays.size(); // 10
Public holidays are different in every country, so Java has no list of them, and we keep our own Set<LocalDate> of holidays and add a second filter.
Set<LocalDate> holidays = Set.of(LocalDate.of(2026, 10, 2));
long count = start.datesUntil(end)
.filter(d -> !weekend.contains(d.getDayOfWeek()))
.filter(d -> !holidays.contains(d))
.count(); // 9
We keep the weekend days in a Set, because some countries have a different weekend, for example Friday and Saturday, and changing the set changes the rule.
The same filter also picks one weekday, so filter(d -> d.getDayOfWeek() == DayOfWeek.MONDAY) returns [2026-10-05, 2026-10-12].
We can also calculate business days with a counting method that builds no list, or check if a date is a weekend for a single date.
7. Ranges of LocalDateTime
A LocalDateTime is a date with a time of day, such as 2026-10-01T22:30. LocalDateTime has no datesUntil() method, so we handle two cases differently.
To list the calendar days that a time range covers, we take the date part with toLocalDate() and then use datesUntil() as before.
LocalDateTime from = LocalDateTime.of(2026, 10, 1, 22, 30);
LocalDateTime to = LocalDateTime.of(2026, 10, 4, 8, 0);
List<LocalDate> days = from.toLocalDate().datesUntil(to.toLocalDate().plusDays(1)).toList();
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04]
To step by hours or minutes, we use the three-argument Stream.iterate(). For example, a medication reminder app needs a reminder time every 12 hours, so each value is 12 hours after the previous one, and the stream stops when the time is no longer before to.
List<LocalDateTime> reminders = Stream.iterate(from, t -> t.isBefore(to), t -> t.plusHours(12)).toList();
// [2026-10-01T22:30, 2026-10-02T10:30, 2026-10-02T22:30,
// 2026-10-03T10:30, 2026-10-03T22:30]
8. Legacy Date and Calendar
Old code often has java.util.Date values. The simplest fix is to convert each Date to a LocalDate first. A Date is a moment in time, so the conversion needs a time zone, and here we use the default zone of the JVM.
ZoneId zone = ZoneId.systemDefault();
LocalDate start = startDate.toInstant().atZone(zone).toLocalDate();
LocalDate end = endDate.toInstant().atZone(zone).toLocalDate();
List<LocalDate> dates = start.datesUntil(end).toList();
// [2026-10-01, 2026-10-02, 2026-10-03, 2026-10-04]
If an old API still needs Date objects, we convert each LocalDate back to a Date with Date.from(d.atStartOfDay(zone).toInstant()).
Code that cannot use java.time at all (Java 7 and older) loops with a Calendar, which adds one day until the date reaches the end.
List<Date> dates = new ArrayList<>();
Calendar calendar = new GregorianCalendar();
calendar.setTime(startDate);
while (calendar.getTime().before(endDate)) {
dates.add(calendar.getTime());
calendar.add(Calendar.DATE, 1);
}
// 4 dates for Oct 1 to Oct 5, 2026
The Calendar loop keeps the time of day of startDate, so when endDate has a later time on the same day, the loop adds one more date. LocalDate has no time part, so datesUntil() has no such problem.
9. Date Range FAQs
Four questions come up often about date ranges in Java, and each answer has a short snippet.
9.1. Is the End Date Included in datesUntil()?
No. The call datesUntil(end) stops one day before end, so to include end, we call datesUntil(end.plusDays(1)). With a step, end is excluded too, which means datesUntil(end.plusDays(1), Period.ofWeeks(1)) includes end only when end is one of the step dates.
9.2. How Do I Get All Months Between Two Dates?
We use YearMonth and the three-argument Stream.iterate(), where isAfter() makes the last month inclusive.
List<YearMonth> months = Stream.iterate(YearMonth.of(2026, 10),
m -> !m.isAfter(YearMonth.of(2027, 1)),
m -> m.plusMonths(1))
.toList();
// [2026-10, 2026-11, 2026-12, 2027-01]
9.3. How Do I Print the Day Name for Each Date?
We map each date to its DayOfWeek and take the name of the day.
List<String> names = start.datesUntil(LocalDate.of(2026, 10, 5))
.map(d -> d.getDayOfWeek().toString())
.toList();
// [THURSDAY, FRIDAY, SATURDAY, SUNDAY]
9.4. Should I Collect a Long Range Into a List?
Only when we need the list, because a range of 10 years has about 3,650 dates, and each one is an object. When we only count, filter or loop over the dates, we keep the datesUntil() stream and call count() or forEach() on it.
10. Conclusion
In Java 9 and later, start.datesUntil(end).toList() lists all dates between two dates, with the start date in the list and the end date left out. We pass end.plusDays(1) for an inclusive range and a Period to step by weeks or months, and a negative Period lists dates in reverse order.
In Java 8, Stream.iterate() with limit() or LongStream.range() gives the same list. For business days, we filter out weekend days and holidays, and we convert LocalDateTime and Date values to LocalDate first.
11. References
- LocalDate JavaDoc (Java 25)
- Period JavaDoc (Java 25)
- ChronoUnit JavaDoc (Java 25)
- Stream JavaDoc (Java 25)
Happy Learning !!