Java’s Date-Time API includes a powerful utility class called TemporalAdjusters
that simplifies complex date manipulations. Instead of manually calculating dates such as “the next Monday” or “the last day of the month,” you can use these pre-built adjusters to write clearer, more readable code.
A Temporal Adjuster is an interface that adjusts a temporal object, such as LocalDate
or LocalDateTime
, according to some rule. The TemporalAdjusters
class provides many built-in implementations of this interface for common calendar calculations.
This approach replaces cumbersome manual calculations with expressive method calls, reducing errors and improving maintainability.
To find the date of the next Monday after a given date:
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class NextMondayExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println("Today: " + today);
System.out.println("Next Monday: " + nextMonday);
}
}
If today is Monday, next()
will return the Monday of the following week.
Similarly, you can find the previous Friday:
LocalDate previousFriday = today.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
If you need to get the next Monday but return today if it is already Monday, use:
LocalDate nextOrSameMonday = today.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
You can easily jump to boundary dates, such as the first day of the current month:
LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
Or the last day of the year:
LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
Simplifies common date logic: Manual date calculations can be error-prone and verbose. Using temporal adjusters provides clear intent with minimal code.
Improves readability: A call like date.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
immediately communicates the intention to readers.
Handles edge cases: Built-in adjusters correctly manage complexities such as leap years, varying month lengths, and daylight saving transitions.
The TemporalAdjusters
class offers an elegant, expressive way to perform common date calculations. Whether you need to find the next occurrence of a weekday, the first day of a month, or the last day of a year, these built-in adjusters simplify your code and reduce bugs. Leveraging them is a best practice for any Java developer working with date and time.
While Java’s built-in TemporalAdjusters
cover many common date manipulation scenarios, there are times when you need custom logic tailored to your application’s requirements. For example, adjusting to the “next working day” (skipping weekends) or moving to a company-specific event date.
This section shows how to create your own custom TemporalAdjuster
by implementing the adjustInto()
method.
A custom TemporalAdjuster
is simply a class or lambda that implements the TemporalAdjuster
interface, which defines a single method:
Temporal adjustInto(Temporal temporal);
This method receives a temporal object (such as a LocalDate
) and returns a new, adjusted temporal instance. Because Java date-time types are immutable, you always return a new adjusted object instead of modifying the input.
Suppose you want to move a date forward to the next weekday, skipping Saturdays and Sundays:
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
public class NextWorkingDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal);
DayOfWeek day = date.getDayOfWeek();
switch (day) {
case FRIDAY:
return date.plusDays(3); // skip Saturday & Sunday
case SATURDAY:
return date.plusDays(2); // skip Sunday
default:
return date.plusDays(1); // next day
}
}
}
Usage:
LocalDate today = LocalDate.of(2025, 5, 30); // Friday
LocalDate nextWorkingDay = today.with(new NextWorkingDayAdjuster());
System.out.println("Next working day after " + today + " is " + nextWorkingDay);
// Output: Next working day after 2025-05-30 is 2025-06-02
This custom adjuster moves the date forward, skipping weekends.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
public class NextWorkingDayExample {
public static void main(String[] args) {
LocalDate today = LocalDate.of(2025, 5, 30); // Friday
LocalDate nextWorkingDay = today.with(new NextWorkingDayAdjuster());
System.out.println("Next working day after " + today + " is " + nextWorkingDay);
// Output: Next working day after 2025-05-30 is 2025-06-02
}
// Custom TemporalAdjuster to skip weekends
static class NextWorkingDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal);
DayOfWeek day = date.getDayOfWeek();
switch (day) {
case FRIDAY:
return date.plusDays(3); // skip Saturday & Sunday
case SATURDAY:
return date.plusDays(2); // skip Sunday
default:
return date.plusDays(1); // next day
}
}
}
}
Imagine a company has quarterly review meetings on the 15th of March, June, September, and December. You want to adjust any date to the next such event date.
import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
public class NextQuarterlyReviewAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal);
int year = date.getYear();
LocalDate[] eventDates = {
LocalDate.of(year, Month.MARCH, 15),
LocalDate.of(year, Month.JUNE, 15),
LocalDate.of(year, Month.SEPTEMBER, 15),
LocalDate.of(year, Month.DECEMBER, 15)
};
for (LocalDate eventDate : eventDates) {
if (!date.isAfter(eventDate)) {
return eventDate;
}
}
// If date is after December 15, move to next year's March 15
return LocalDate.of(year + 1, Month.MARCH, 15);
}
}
Usage:
LocalDate today = LocalDate.of(2025, 6, 20);
LocalDate nextReview = today.with(new NextQuarterlyReviewAdjuster());
System.out.println("Next quarterly review after " + today + " is " + nextReview);
// Output: Next quarterly review after 2025-06-20 is 2025-09-15
import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
public class NextQuarterlyReviewExample {
public static void main(String[] args) {
LocalDate today = LocalDate.of(2025, 6, 20);
LocalDate nextReview = today.with(new NextQuarterlyReviewAdjuster());
System.out.println("Next quarterly review after " + today + " is " + nextReview);
// Output: Next quarterly review after 2025-06-20 is 2025-09-15
}
// Custom TemporalAdjuster for quarterly review dates
static class NextQuarterlyReviewAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal);
int year = date.getYear();
LocalDate[] eventDates = {
LocalDate.of(year, Month.MARCH, 15),
LocalDate.of(year, Month.JUNE, 15),
LocalDate.of(year, Month.SEPTEMBER, 15),
LocalDate.of(year, Month.DECEMBER, 15)
};
for (LocalDate eventDate : eventDates) {
if (!date.isAfter(eventDate)) {
return eventDate;
}
}
// If date is after December 15, move to next year's March 15
return LocalDate.of(year + 1, Month.MARCH, 15);
}
}
}
Immutability: Date-time objects are immutable, so your custom adjusters must always return new instances rather than modifying the original. This prevents unexpected side effects and ensures thread safety.
Reusability: Custom adjusters should be stateless when possible so they can be reused across your application without risk of data corruption. If state is needed (e.g., company event dates), consider making the adjuster immutable and thread-safe.
Convenience: You can implement custom adjusters as separate classes, anonymous classes, or lambdas for concise inline adjustments.
Custom TemporalAdjusters
empower you to encapsulate complex, domain-specific date logic cleanly. By implementing the adjustInto()
method, you can create reusable, immutable adjusters—whether for skipping weekends, scheduling company events, or any other specialized temporal rule—making your date handling code easier to understand and maintain.
In real-world applications, date calculations often involve business rules like scheduling paydays, identifying specific weekdays, or skipping weekends and holidays. Java’s TemporalAdjusters
—both built-in and custom—provide elegant solutions that keep your code clean, readable, and maintainable.
Below are practical examples illustrating how to apply these adjusters for common scenarios.
Let’s say payday is on the 25th of every month. However, if the 25th falls on a weekend, the payday moves to the previous Friday (the last business day before the 25th).
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class PaydayExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate payday = today.withDayOfMonth(25);
// If payday is Saturday, adjust to Friday (previous day)
if (payday.getDayOfWeek() == DayOfWeek.SATURDAY) {
payday = payday.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
// If payday is Sunday, adjust to Friday (two days before)
else if (payday.getDayOfWeek() == DayOfWeek.SUNDAY) {
payday = payday.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
System.out.println("Next payday: " + payday);
}
}
Reflection: Using built-in adjusters like previous()
lets you quickly handle weekend adjustments, avoiding manual calculations.
To find the first Friday of the current month, use:
LocalDate firstFriday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY));
System.out.println("First Friday of this month: " + firstFriday);
This is useful for scheduling monthly events like team meetings or reporting deadlines.
If the last day of the month falls on a weekend, the last business day is the previous Friday. Here’s a custom adjuster for that:
import java.time.LocalDate;
import java.time.DayOfWeek;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
public class LastBusinessDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal).with(TemporalAdjusters.lastDayOfMonth());
DayOfWeek day = date.getDayOfWeek();
if (day == DayOfWeek.SATURDAY) {
return date.minusDays(1);
} else if (day == DayOfWeek.SUNDAY) {
return date.minusDays(2);
}
return date;
}
}
// Usage:
LocalDate lastBusinessDay = LocalDate.now().with(new LastBusinessDayAdjuster());
System.out.println("Last business day of the month: " + lastBusinessDay);
Recall the NextWorkingDayAdjuster
that skips weekends:
LocalDate today = LocalDate.of(2025, 5, 30); // Friday
LocalDate nextWorkingDay = today.with(new NextWorkingDayAdjuster());
System.out.println("Next working day after " + today + " is " + nextWorkingDay);
This adjuster simplifies scheduling tasks that should run only on business days.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
public class BusinessDayAdjusterExample {
public static void main(String[] args) {
// Example 1: Last business day of the current month
LocalDate today = LocalDate.now();
LocalDate lastBusinessDay = today.with(new LastBusinessDayAdjuster());
System.out.println("Last business day of the month: " + lastBusinessDay);
// Example 2: Next working day from a fixed date
LocalDate specificDay = LocalDate.of(2025, 5, 30); // Friday
LocalDate nextWorkingDay = specificDay.with(new NextWorkingDayAdjuster());
System.out.println("Next working day after " + specificDay + " is " + nextWorkingDay);
}
// Adjuster for the last business day (Mon–Fri) of the month
static class LastBusinessDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal).with(TemporalAdjusters.lastDayOfMonth());
DayOfWeek day = date.getDayOfWeek();
if (day == DayOfWeek.SATURDAY) {
return date.minusDays(1); // move to Friday
} else if (day == DayOfWeek.SUNDAY) {
return date.minusDays(2); // move to Friday
}
return date; // already a business day
}
}
// Adjuster to get the next working day (skip weekends)
static class NextWorkingDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = LocalDate.from(temporal);
DayOfWeek day = date.getDayOfWeek();
switch (day) {
case FRIDAY:
return date.plusDays(3); // skip Saturday & Sunday
case SATURDAY:
return date.plusDays(2); // skip Sunday
default:
return date.plusDays(1); // next day
}
}
}
}
date.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY))
clearly state the intent, unlike manual date arithmetic.Using both built-in and custom TemporalAdjusters
helps developers write concise, expressive, and reliable date-time code. From scheduling paydays to identifying special weekdays or business boundaries, adjusters encapsulate complex date logic, enabling you to focus on your application’s core logic rather than error-prone manual calculations.