skip to content

How do you create, access, and combine LocalDate/LocalTime/LocalDateTime, given that they are immutable?

level: middleimportance: must knowfreq 65%

answer

  1. of / now / parse to create
  2. now() reads system clock + default zone
  3. plus/minus/with return NEW objects
  4. atTime / atStartOfDay to combine; toLocalDate/Time to split
  5. Immutable ⇒ thread-safe, must reassign result

basics

~20 s

Create them with factory methods like LocalDate.of(...), LocalTime.now(), or by parsing a string. Read parts with getters such as getYear() or getHour(). They never change in place — methods like plusDays or atTime return a brand-new object you must assign.

solid answer

~40 s

All three types are immutable, so you build them with static factory methods rather than constructors: of(...) for explicit values, now() for the current value (which reads the system clock and default zone), and parse(...) for ISO-8601 text. You read fields with getters — getYear/getMonthValue/getDayOfMonth on dates, getHour/getMinute/getSecond on times. Because they are immutable, every with*, plus*, and minus* method returns a NEW instance; the original is unchanged, so you must capture the result (date = date.plusDays(1)). Combining is explicit: LocalDate.atTime(LocalTime) or atStartOfDay() yields a LocalDateTime; LocalDateTime.toLocalDate()/toLocalTime() splits it back out. To move onto the global timeline you call atZone(ZoneId) or atOffset(ZoneOffset). Immutability makes them inherently thread-safe and safe to share, cache, and use as map keys.

code

java · 10 lines
java
LocalDate d = LocalDate.of(2026, 1, 1);
// BUG: result discarded, d unchanged
d.plusDays(10);
// Correct: reassign
d = d.plusDays(10);              // 2026-01-11

LocalDate today = LocalDate.now();              // system clock + default zone
LocalDateTime deadline = today.atTime(17, 0);   // 5pm, no zone
LocalTime t = deadline.toLocalTime();           // split back out: 17:00
int year = deadline.getYear();

go deeper

for a junior

Can create with of/now/parse, read a field with a getter, and knows you must reassign the result of plusDays.

for a middle

Explains immutability and its thread-safety consequence, uses with*/plus*/minus* fluently, and combines/splits via atTime/atStartOfDay/toLocalDate.

for a senior

Knows now() depends on the system clock + default zone and injects a Clock for testability; uses TemporalAdjusters and reasons about atZone/atOffset boundaries.

for a principal

Defines team patterns: inject Clock everywhere for deterministic time, avoid default-zone now() in domain logic, and treat these immutable values as safe to share/cache across threads.

## Immutability is the central design idea Every java.time value type is **immutable**: its internal state can never change after construction. There are no setters. Any method that looks like it modifies the value actually returns a **new** object and leaves the original untouched. The big benefits: the objects are automatically **thread-safe**, safe to share freely, and safe to use as keys in maps/sets. The single most common beginner mistake follows directly: ```java LocalDate d = LocalDate.of(2026, 1, 1); d.plusDays(10); // BUG: result is thrown away System.out.println(d); // still 2026-01-01 ``` You must capture the result: `d = d.plusDays(10);`. ## Creating values (factory methods, not constructors) - **`of(...)`** — explicit components, validated. `LocalDate.of(2026, 2, 14)`, `LocalTime.of(9, 30)`, `LocalDateTime.of(2026, 2, 14, 9, 30)`. - **`now()`** — the current value. Note `now()` reads the **system clock and the JVM default time zone** to decide today's date/time, even though the *result* itself is zone-less. For testability, prefer `now(Clock)` so you can inject a fixed clock. - **`parse(CharSequence)`** — from ISO-8601 text: `LocalDate.parse("2026-02-14")`, `LocalTime.parse("09:30")`, `LocalDateTime.parse("2026-02-14T09:30")`. A custom `DateTimeFormatter` overload handles other formats. ## Accessing fields Use the getters: `getYear()`, `getMonth()`/`getMonthValue()`, `getDayOfMonth()`, `getDayOfWeek()`, `getDayOfYear()` for dates; `getHour()`, `getMinute()`, `getSecond()`, `getNano()` for times. `LocalDateTime` has both sets. ## Modifying = deriving a new value - **`plusX` / `minusX`**: `plusDays`, `minusMonths`, `plusHours`, etc. - **`withX`**: set one field, returning a copy: `date.withYear(2030)`, `time.withHour(0)`. - **`with(TemporalAdjuster)`**: higher-level adjustments, e.g. `date.with(TemporalAdjusters.lastDayOfMonth())`. All return a new object; chain them: `date.plusMonths(1).withDayOfMonth(1)`. ## Combining and splitting - `LocalDate.atTime(LocalTime)` or `atTime(9, 30)` → `LocalDateTime`. - `LocalDate.atStartOfDay()` → `LocalDateTime` at 00:00. - `LocalTime.atDate(LocalDate)` → `LocalDateTime`. - `LocalDateTime.toLocalDate()` / `toLocalTime()` split it back. - `atZone(ZoneId)` / `atOffset(ZoneOffset)` move it onto the timeline (giving a `ZonedDateTime` / `OffsetDateTime`). ## Terms defined - **Factory method**: a static method that creates and returns an instance (e.g. `of`, `now`, `parse`), used instead of a public constructor. - **TemporalAdjuster**: a strategy object encapsulating a date adjustment (e.g. "next Monday", "last day of month"). - **Clock**: an abstraction over "the current instant + zone"; injecting one makes `now()` deterministic in tests. ## Worked example ```java LocalDate today = LocalDate.now(); // reads system clock + default zone LocalDate due = today.plusWeeks(2).with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); LocalDateTime deadline = due.atTime(17, 0); // 5pm on that date (no zone) int year = deadline.getYear(); LocalTime onlyTime = deadline.toLocalTime(); // 17:00 ```

  • Why might calling date.plusDays(1) appear to 'do nothing'?
    Because these types are immutable: plusDays returns a new object and leaves the original unchanged. If you don't assign the result (date = date.plusDays(1)), the change is lost.
  • How would you make LocalDate.now() deterministic in a unit test?
    Use the now(Clock) overload and inject a fixed Clock, e.g. LocalDate.now(Clock.fixed(instant, zone)), so it doesn't depend on the real system clock or default zone.

saying these in an interview costs you the question

  • Calling plus/minus/with and not assigning the result (expecting in-place mutation)
  • Trying to use a public constructor (there are none; use of/now/parse)
  • Assuming now() is zone-independent — it reads the JVM default zone to pick today
  • Adding synchronization around these objects (unnecessary; they are immutable/thread-safe)

context