Java TemporalAdjusters: Built-in and Custom Date Adjusters

Java TemporalAdjusters examples for month ends, next weekdays and the second Tuesday, plus custom adjusters for working days, paydays and anniversaries.

October 2026 calendar starting from Wednesday the 14th, with the dates returned by firstDayOfMonth, firstInMonth Monday, dayOfWeekInMonth 2 Tuesday, next Friday, previous Wednesday, lastInMonth Friday and lastDayOfMonth highlighted

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.

October 2026 calendar starting from Wednesday the 14th, with the dates returned by firstDayOfMonth, firstInMonth Monday, dayOfWeekInMonth 2 Tuesday, next Friday, previous Wednesday, lastInMonth Friday and lastDayOfMonth highlighted
Each adjuster in TemporalAdjusters picks one date in the calendar relative to the input date, October 14, 2026
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.

AdjusterReturnsResult
firstDayOfMonth()Day 1 of the same month2026-10-01
lastDayOfMonth()Last day of the same month2026-10-31
firstDayOfNextMonth()Day 1 of the next month2026-11-01
firstDayOfYear()January 1 of the same year2026-01-01
lastDayOfYear()December 31 of the same year2026-12-31
firstDayOfNextYear()January 1 of the next year2027-01-01
firstInMonth(MONDAY)First Monday in the same month2026-10-05
lastInMonth(FRIDAY)Last Friday in the same month2026-10-30
dayOfWeekInMonth(2, TUESDAY)Second Tuesday in the same month2026-10-13
next(WEDNESDAY)Next Wednesday after the date2026-10-21
nextOrSame(WEDNESDAY)The date itself if it is a Wednesday, else the next one2026-10-14
previous(WEDNESDAY)Previous Wednesday before the date2026-10-07
previousOrSame(WEDNESDAY)The date itself if it is a Wednesday, else the previous one2026-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 !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.