A TemporalAdjuster in Java is a rule that moves a date to another date, such as the last day of the month or the next Friday, and the TemporalAdjusters class provides the common rules as ready-made factory methods. We pass an adjuster to the with() method of a date, which returns a new adjusted date and leaves the original as it is.
Adjusters calculate recurring dates in business code, for example the payday at the end of each month, a team meeting every Monday, a maintenance window on the second Tuesday, or the first day of a reporting month.
The following example applies five built-in adjusters to Wednesday, October 14, 2026.
LocalDate today = LocalDate.of(2026, 10, 14); // 2026-10-14, a Wednesday
LocalDate monthEnd = today.with(TemporalAdjusters.lastDayOfMonth()); // 2026-10-31
LocalDate nextFriday = today.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); // 2026-10-16
LocalDate firstMonday = today.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2026-10-05
LocalDate secondTuesday = today.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY)); // 2026-10-13
LocalDate nextYear = today.with(TemporalAdjusters.firstDayOfNextYear()); // 2027-01-01
Notice that each call returns a new LocalDate and the variable today still holds October 14. We first look at how with() runs an adjuster, go through every built-in adjuster with its result, apply adjusters to date-times, and write custom adjusters for payroll and meeting dates.
1. Applying an Adjuster With with() and adjustInto()
The TemporalAdjuster interface is a functional interface with one method, adjustInto(Temporal), which takes a date or date-time and returns the adjusted copy. The call date.with(adjuster) calls adjuster.adjustInto(date) for us and casts the result back to the type of date, so we get a LocalDate from a LocalDate. The with() form is the recommended one, because it reads better.

LocalDate date = LocalDate.of(2026, 10, 14);
LocalDate viaWith = date.with(TemporalAdjusters.lastDayOfMonth()); // 2026-10-31
LocalDate viaAdjustInto = (LocalDate) TemporalAdjusters.lastDayOfMonth().adjustInto(date); // 2026-10-31
Several java.time classes are adjusters themselves. A DayOfWeek moves the date within the same Monday-to-Sunday week, a Month changes the month and clamps the day, and a LocalTime or a YearMonth replaces the matching fields.
LocalDate wednesday = LocalDate.of(2026, 10, 14);
LocalDate sameWeekMonday = wednesday.with(DayOfWeek.MONDAY); // 2026-10-12
LocalDate sameWeekSunday = wednesday.with(DayOfWeek.SUNDAY); // 2026-10-18
LocalDate february = LocalDate.of(2026, 3, 31).with(Month.FEBRUARY); // 2026-02-28
LocalDate otherMonth = wednesday.with(YearMonth.of(2027, 2)); // 2027-02-14
LocalDateTime atNoon = LocalDateTime.of(2026, 10, 14, 9, 30).with(LocalTime.NOON); // 2026-10-14T12:00
2. Predefined Adjusters
The TemporalAdjusters class has 14 static factory methods, and the table lists all of them with the result for Wednesday, October 14, 2026. The adjusters returned by these methods are immutable and thread-safe, so we can keep them in static final fields and share them.
| Adjuster | Returns | Result |
|---|---|---|
| firstDayOfMonth() | Day 1 of the same month | 2026-10-01 |
| lastDayOfMonth() | Last day of the same month | 2026-10-31 |
| firstDayOfNextMonth() | Day 1 of the next month | 2026-11-01 |
| firstDayOfYear() | January 1 of the same year | 2026-01-01 |
| lastDayOfYear() | December 31 of the same year | 2026-12-31 |
| firstDayOfNextYear() | January 1 of the next year | 2027-01-01 |
| firstInMonth(MONDAY) | First Monday in the same month | 2026-10-05 |
| lastInMonth(FRIDAY) | Last Friday in the same month | 2026-10-30 |
| dayOfWeekInMonth(2, TUESDAY) | Second Tuesday in the same month | 2026-10-13 |
| next(WEDNESDAY) | Next Wednesday after the date | 2026-10-21 |
| nextOrSame(WEDNESDAY) | The date itself if it is a Wednesday, else the next one | 2026-10-14 |
| previous(WEDNESDAY) | Previous Wednesday before the date | 2026-10-07 |
| previousOrSame(WEDNESDAY) | The date itself if it is a Wednesday, else the previous one | 2026-10-14 |
| ofDateAdjuster(operator) | Wraps our own UnaryOperator<LocalDate> | See section 4 |
2.1. First and Last Day of a Month or Year
The month and year adjusters know the length of every month, including February in leap years, so we never calculate 28, 30 or 31 ourselves. The article on the first and last day of a week, month or year covers week boundaries and the start and end time of each day.
LocalDate leapFebruary = LocalDate.of(2028, 2, 10).with(TemporalAdjusters.lastDayOfMonth()); // 2028-02-29
LocalDate yearEnd = LocalDate.of(2026, 10, 14).with(TemporalAdjusters.lastDayOfYear()); // 2026-12-31
LocalDate nextMonthStart = LocalDate.of(2026, 12, 20).with(TemporalAdjusters.firstDayOfNextMonth()); // 2027-01-01
For a quarter, there is no built-in adjuster, but the field IsoFields.DAY_OF_QUARTER gives the start with date.with(IsoFields.DAY_OF_QUARTER, 1). The quarter start and end dates article has the end date as well.
2.2. next(), previous() and the OrSame Variants
The adjusters next() and previous() always move at least one day, so on a Wednesday, next(WEDNESDAY) returns the Wednesday one week later. The OrSame variants return the input date when it already falls on the requested day, which is what a rule such as “the meeting is on Monday, or today if today is Monday” needs.
LocalDate wednesday = LocalDate.of(2026, 10, 14);
LocalDate nextWednesday = wednesday.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY)); // 2026-10-21
LocalDate sameWednesday = wednesday.with(TemporalAdjusters.nextOrSame(DayOfWeek.WEDNESDAY)); // 2026-10-14
LocalDate lastMonday = wednesday.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)); // 2026-10-12
LocalDate comingMonday = wednesday.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY)); // 2026-10-19
The same adjusters answer questions such as tomorrow, yesterday and the next weekday in the next and previous date article.
2.3. Ordinal Weekdays Such as the Second Tuesday
The adjusters firstInMonth() and lastInMonth() find the first and last given weekday of the month. The general form dayOfWeekInMonth(ordinal, dayOfWeek) takes the position as a number, where 1 is the first, -1 is the last and -2 is the second to last.
LocalDate october = LocalDate.of(2026, 10, 1);
LocalDate firstMonday = october.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2026-10-05
LocalDate lastFriday = october.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); // 2026-10-30
LocalDate lastThursday = october.with(TemporalAdjusters.dayOfWeekInMonth(-1, DayOfWeek.THURSDAY)); // 2026-10-29
LocalDate fifthMonday = october.with(TemporalAdjusters.dayOfWeekInMonth(5, DayOfWeek.MONDAY)); // 2026-11-02
An ordinal past the end of the month does not throw an exception; it moves into the next month. October 2026 has four Mondays, so the fifth Monday is November 2. When a rule must stay in the same month, we compare the month of the result with the input month, or use lastInMonth() for “the last Monday”.
3. Adjusting LocalDateTime and ZonedDateTime Values
The built-in adjusters change only the date fields, so a LocalDateTime keeps its time of day and a ZonedDateTime keeps its zone. A zoned value can still get a different offset, because the new date can fall on the other side of a daylight saving change.
LocalDateTime meeting = LocalDateTime.of(2026, 10, 14, 9, 30);
LocalDateTime monthEndMeeting = meeting.with(TemporalAdjusters.lastDayOfMonth()); // 2026-10-31T09:30
ZonedDateTime berlin = ZonedDateTime.of(2026, 10, 14, 9, 30, 0, 0, ZoneId.of("Europe/Berlin"));
ZonedDateTime berlinMonthEnd = berlin.with(TemporalAdjusters.lastDayOfMonth()); // 2026-10-31T09:30+01:00[Europe/Berlin]
Berlin moves from summer time (+02:00) to winter time (+01:00) on October 25, 2026, so the result shows +01:00 while the clock time stays 09:30. Types without a date reject date adjusters with an exception, because they have no day-of-month field.
LocalTime noon = LocalTime.NOON.with(TemporalAdjusters.lastDayOfMonth()); // UnsupportedTemporalTypeException
Instant instant = Instant.parse("2026-10-14T00:00:00Z").with(TemporalAdjusters.lastDayOfMonth()); // UnsupportedTemporalTypeException
For an Instant, we convert to a ZonedDateTime in the zone where the month ends, apply the adjuster, and call toInstant() on the result.
4. Custom Adjusters
When no built-in adjuster fits a business rule, we write our own and give it a name, so the rule is defined in one place and reads like the built-ins at every call site. There are two ways to create one.
- The method TemporalAdjusters.ofDateAdjuster() or a lambda creates a small adjuster inline.
- A class or record that implements TemporalAdjuster suits rules that need parameters or tests of their own.
4.1. Next Working Day with ofDateAdjuster()
The method ofDateAdjuster() takes a UnaryOperator<LocalDate>, so our code works with a plain LocalDate instead of the general Temporal type. The returned adjuster also works on LocalDateTime and ZonedDateTime, where it changes the date and keeps the time.
static TemporalAdjuster nextWorkingDay() {
return TemporalAdjusters.ofDateAdjuster(date -> {
LocalDate next = date.plusDays(1);
while (next.getDayOfWeek() == DayOfWeek.SATURDAY || next.getDayOfWeek() == DayOfWeek.SUNDAY) {
next = next.plusDays(1);
}
return next;
});
}
LocalDate afterFriday = LocalDate.of(2026, 10, 16).with(nextWorkingDay()); // 2026-10-19
LocalDate afterTuesday = LocalDate.of(2026, 10, 13).with(nextWorkingDay()); // 2026-10-14
LocalDateTime withTime = LocalDateTime.of(2026, 10, 16, 17, 0).with(nextWorkingDay()); // 2026-10-19T17:00
A plain lambda such as t -> t.plus(Period.ofDays(7)) also compiles, because TemporalAdjuster is a functional interface, but it receives a Temporal and can call only the general methods. The nextWorkingDay() rule also ignores public holidays, and the business days article adds a holiday set and the weekend check from check if a date is a weekend.
4.2. Implementing the TemporalAdjuster Interface
A common first attempt at a “next anniversary” adjuster sets the month and the day to fixed values. That code returns a date in the past whenever the input is later in the year than the anniversary.
TemporalAdjuster naive = t -> t.with(ChronoField.MONTH_OF_YEAR, 11).with(ChronoField.DAY_OF_MONTH, 22);
LocalDate wrong = LocalDate.of(2026, 12, 1).with(naive); // 2026-11-22, which is already in the past
The record NextAnniversary takes the anniversary as a MonthDay and returns the next occurrence after the input date. The method MonthDay.atYear() turns February 29 into February 28 in years that are not leap years, and temporal.with(candidate) keeps the time when the input is a date-time.
record NextAnniversary(MonthDay day) implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal);
LocalDate candidate = day.atYear(date.getYear());
if (!candidate.isAfter(date)) {
candidate = day.atYear(date.getYear() + 1);
}
return temporal.with(candidate);
}
}
TemporalAdjuster contractDay = new NextAnniversary(MonthDay.of(11, 22));
LocalDate beforeIt = LocalDate.of(2026, 10, 14).with(contractDay); // 2026-11-22
LocalDate afterIt = LocalDate.of(2026, 12, 1).with(contractDay); // 2027-11-22
LocalDate leapBirthday = LocalDate.of(2026, 3, 1).with(new NextAnniversary(MonthDay.of(2, 29))); // 2027-02-28
5. Payday and Meeting Dates in a Payroll App
A payroll app pays salaries on the last working day of each month. When the month ends on a Saturday or a Sunday, the payment moves back to the Friday before. The same app also lists the next team meetings, which take place every Monday.
The payday rule combines lastDayOfMonth() with previous(FRIDAY) inside one named adjuster, so the scheduler, the payslip and the reports all use the same rule.
static TemporalAdjuster lastWorkingDayOfMonth() {
return TemporalAdjusters.ofDateAdjuster(date -> {
LocalDate last = date.with(TemporalAdjusters.lastDayOfMonth());
return switch (last.getDayOfWeek()) {
case SATURDAY, SUNDAY -> last.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
default -> last;
};
});
}
static List<LocalDate> nextMondays(LocalDate from, int count) {
return Stream.iterate(from.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY)), d -> d.plusWeeks(1))
.limit(count)
.toList();
}
LocalDate octoberPay = LocalDate.of(2026, 10, 1).with(lastWorkingDayOfMonth()); // 2026-10-30
LocalDate novemberPay = LocalDate.of(2026, 11, 1).with(lastWorkingDayOfMonth()); // 2026-11-30
LocalDate januaryPay = LocalDate.of(2027, 1, 15).with(lastWorkingDayOfMonth()); // 2027-01-29
List<LocalDate> meetings = nextMondays(LocalDate.of(2026, 10, 14), 4); // [2026-10-19, 2026-10-26, 2026-11-02, 2026-11-09]
October 31, 2026 is a Saturday and January 31, 2027 is a Sunday, so both paydays move to the Friday before. The stream starts with nextOrSame(MONDAY), so a list created on a Monday includes that day.
6. TemporalAdjusters FAQs
Questions about adjusters mostly concern the OrSame variants, thread safety, quarters and the related TemporalQuery interface.
6.1. What is the difference between next() and nextOrSame()?
The adjuster next(day) always returns a later date, even when the input falls on that day. The adjuster nextOrSame(day) returns the input date when it already is that day. The same difference applies to previous() and previousOrSame().
6.2. Are TemporalAdjusters thread-safe?
Yes. All adjusters returned by TemporalAdjusters are immutable and thread-safe. A custom adjuster is thread-safe when it holds no mutable state, which is the case for the lambda and the record in section 4.
6.3. How do we get the first day of the quarter?
There is no firstDayOfQuarter() adjuster, so we set the quarter field instead.
LocalDate quarterStart = LocalDate.of(2026, 10, 14).with(IsoFields.DAY_OF_QUARTER, 1); // 2026-10-01
6.4. What is the difference between TemporalAdjuster and TemporalQuery?
A TemporalAdjuster returns a changed date of the same type, whereas a TemporalQuery reads information from a date, such as the quarter or whether the date falls on a weekend, and returns any type.
7. Conclusion
A TemporalAdjuster turns one date into another, and date.with(adjuster) applies it without changing the original. The TemporalAdjusters class covers month and year boundaries, next and previous weekdays, and ordinal weekdays such as the second Tuesday, where an ordinal past the month end moves into the next month.
For business rules, we wrap the logic in ofDateAdjuster() or in a class that implements the interface, and keep it in one named method. More date calculations are collected in the Java date-time tutorials.
8. References
Happy Learning !!