How do you write a custom TemporalAdjuster, for example a next-business-day rule?
answer
- TemporalAdjuster is a functional interface: adjustInto(Temporal)
- ofDateAdjuster(UnaryOperator<LocalDate>) = the easy LocalDate path
- Direct adjustInto works across all Temporal types via with/plus
- Keep adjusters pure/stateless -> thread-safe & reusable
- Capture holiday set in the closure for working-day logic
basics
~20 sTemporalAdjuster has one method, so you can write it as a lambda. The easiest way is TemporalAdjusters.ofDateAdjuster, which takes a function from LocalDate to LocalDate; inside it you check the weekday and add the right number of days to skip the weekend.
solid answer
~40 sBecause TemporalAdjuster is a functional interface (single method adjustInto(Temporal)), you can implement it with a lambda. For date-only logic the cleanest helper is TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate>), which lets you work with LocalDate instead of the raw Temporal and handles the casting. A next-business-day adjuster inspects getDayOfWeek and adds 1, 2, or 3 days so Friday/Saturday/Sunday land on the following Monday. You then apply it like any built-in: date.with(nextBusinessDay). Keep adjusters pure and stateless so they are reusable and thread-safe; java.time immutability gives you that for free. If you implement adjustInto directly (not via ofDateAdjuster) you must accept and return Temporal and typically use temporal.with(field, value) or plus to build the result, so the adjuster works across temporal types. Holiday-aware variants usually capture a holiday set in the lambda's closure.
code
java · 11 linesTemporalAdjuster nextBusinessDay = TemporalAdjusters.ofDateAdjuster(date -> {
int add = switch (date.getDayOfWeek()) {
case FRIDAY -> 3;
case SATURDAY -> 2;
default -> 1;
};
return date.plusDays(add);
});
LocalDate friday = LocalDate.of(2026, 6, 19);
LocalDate result = friday.with(nextBusinessDay); // 2026-06-22 (Monday)go deeper
Recognizes you can pass a lambda but typically reaches for built-ins; may not know ofDateAdjuster.
Writes a basic custom adjuster via ofDateAdjuster and applies it with with.
Designs pure, type-preserving, testable adjusters (incl. holiday-aware), and knows when a built-in already suffices.
Encapsulates organizational calendar rules as a small reusable adjuster library, enforces purity/immutability conventions, and weighs build-vs-reuse and locale concerns.
## Why custom adjusters exist The built-in `TemporalAdjusters` cover calendar positions (ends of months, n-th weekdays) but not domain rules like "next business day," "skip company holidays," or "next quarter end." Since `TemporalAdjuster` is a **functional interface** — exactly one abstract method, `Temporal adjustInto(Temporal temporal)` — you can supply your own rule as a lambda or class. ## The two ways to write one **1. The easy way: `ofDateAdjuster`.** `TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate>)` wraps a `LocalDate -> LocalDate` function into a `TemporalAdjuster`. You only deal with `LocalDate`, no casting: ``` TemporalAdjuster nextBusinessDay = TemporalAdjusters.ofDateAdjuster(date -> { int add = switch (date.getDayOfWeek()) { case FRIDAY -> 3; case SATURDAY -> 2; default -> 1; }; return date.plusDays(add); }); LocalDate next = LocalDate.of(2026, 6, 19) // a Friday .with(nextBusinessDay); // 2026-06-22, the Monday ``` **2. The raw way: implement `adjustInto` directly.** Useful when you want the adjuster to work on any `Temporal` (LocalDateTime, ZonedDateTime), not just dates: ``` TemporalAdjuster nbd = temporal -> { DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); int add = switch (dow) { case FRIDAY -> 3; case SATURDAY -> 2; default -> 1; }; return temporal.plus(add, ChronoUnit.DAYS); }; ``` Here `temporal.get(ChronoField...)` reads a field generically and `temporal.plus(amount, unit)` returns the adjusted temporal of the same concrete type. `ChronoField` is the enum of date/time fields; `ChronoUnit` the enum of units. ## Holiday-aware version Capture a set of holidays in the closure and loop until you land on a working day: ``` static TemporalAdjuster nextWorkingDay(Set<LocalDate> holidays) { return TemporalAdjusters.ofDateAdjuster(date -> { LocalDate d = date; do { d = d.plusDays(1); } while (d.getDayOfWeek() == DayOfWeek.SATURDAY || d.getDayOfWeek() == DayOfWeek.SUNDAY || holidays.contains(d)); return d; }); } ``` ## Design properties to respect - **Purity / statelessness.** An adjuster should be a pure function of its input (plus immutable captured data). Don't mutate shared state inside it. Because java.time values are immutable, a stateless adjuster is automatically thread-safe and freely reusable as a constant. - **Return the same concrete type.** When implementing `adjustInto` directly, build the result from the incoming `temporal` (via `with`/`plus`) so a `ZonedDateTime` in returns a `ZonedDateTime` out — don't hardcode `LocalDate`. - **Composability.** Custom adjusters chain with built-ins and with `truncatedTo`, e.g. `dt.with(nextBusinessDay).truncatedTo(ChronoUnit.DAYS)`. - **Testability.** Small named adjusters are trivial to unit-test across the weekend/holiday boundaries that cause production bugs. ## When NOT to roll your own If a built-in already expresses the rule (e.g. `previousOrSame(MONDAY)` for week start), prefer it — it's tested and self-documenting. Reserve custom adjusters for genuinely domain-specific calendar logic.
- Why is a stateless adjuster safe to store as a static constant and share across threads?It is a pure function and java.time values are immutable, so there is no shared mutable state; the same instance can be reused concurrently without synchronization.
- When would you implement adjustInto directly instead of using ofDateAdjuster?When the adjuster must work on time-bearing types like LocalDateTime or ZonedDateTime, not just LocalDate. Building the result from the incoming Temporal preserves its concrete type.
saying these in an interview costs you the question
- Mutating external state inside an adjuster (breaks reuse/thread-safety)
- Hardcoding LocalDate in adjustInto so it fails for ZonedDateTime
- Reinventing a built-in (e.g. week-start) instead of previousOrSame
- Forgetting weekends/holidays when computing 'next business day'