What is DateTimeParseException, and how do lenient, smart, and strict ResolverStyles change parsing behavior?
answer
- Parse = two phases: extract fields, then resolve fields
- DateTimeParseException is unchecked; carries input + error index
- STRICT: rejects out-of-range (Feb 31 -> throw)
- SMART (default): clamps day to month end (Feb 31 -> Feb 28)
- LENIENT: overflow rolls over (Feb 31 -> Mar 3)
- STRICT often needs uuuu instead of yyyy
basics
~10 sParsing bad text throws DateTimeParseException. The ResolverStyle decides how strict validation is: STRICT rejects out-of-range values, SMART (the default) corrects mild ones, LENIENT rolls overflow over (e.g. day 32 -> next month).
solid answer
~50 sWhen text doesn't match a formatter or yields an invalid date, parsing throws DateTimeParseException (an unchecked exception carrying the input and error index). Parsing actually has two phases: (1) parse the text into raw field values, then (2) resolve those fields into an actual date/time value. The ResolverStyle controls phase 2. STRICT validates every field against its real range and the calendar (rejects month 13, day 31 in February, 24:00, etc., and is picky about year-of-era vs era). SMART, the default, fixes obviously-meant-but-slightly-off values (e.g. day 31 in a 30-day month clamps to 30) without rolling over. LENIENT performs arithmetic overflow: day 32 becomes the 1st of next month, month 13 becomes January of next year. You select it with formatter.withResolverStyle(ResolverStyle.STRICT). For untrusted input, prefer STRICT so malformed dates fail loudly instead of silently becoming a different valid date.
code
java · 14 linesimport java.time.*;
import java.time.format.*;
DateTimeFormatter smart = DateTimeFormatter.ofPattern("uuuu-MM-dd"); // SMART by default
DateTimeFormatter strict = smart.withResolverStyle(ResolverStyle.STRICT);
DateTimeFormatter lenient = smart.withResolverStyle(ResolverStyle.LENIENT);
System.out.println(LocalDate.parse("2026-02-31", smart)); // 2026-02-28 (clamped)
System.out.println(LocalDate.parse("2026-02-31", lenient)); // 2026-03-03 (rolled)
try {
LocalDate.parse("2026-02-31", strict); // throws
} catch (DateTimeParseException e) {
System.out.println("rejected at index " + e.getErrorIndex());
}go deeper
Knows parsing bad input throws DateTimeParseException and that you should catch it.
Knows the default is forgiving and that you can make parsing strict; can catch the exception and report it.
Explains the parse-then-resolve model, the three resolver styles with concrete clamp-vs-roll outcomes, and chooses STRICT for untrusted input.
Sets input-validation policy (STRICT + uuuu for external boundaries), reasons about silent-transformation security/correctness risks, and codifies it in shared parsing utilities and tests.
## The two phases of parsing Parsing a date string is **two steps**, and understanding them is the key to resolver styles: 1. **Parse (text -> fields):** the formatter reads the characters and extracts raw field values into a temporary bag (a `TemporalAccessor` of parsed fields). E.g. from `"2026-02-31"` it extracts year=2026, month=2, day=31. At this point no validity check of the *combination* has happened. 2. **Resolve (fields -> value):** those fields are combined into a real `LocalDate`/`LocalTime`/etc. February has no 31st, so something must decide what to do. **That decision is the ResolverStyle.** If either phase fails, you get a **`DateTimeParseException`** — an unchecked (runtime) exception. It carries the original input text and the index where parsing failed, so it's useful for diagnostics. It extends `DateTimeException`. Because it's unchecked, the compiler won't force you to catch it; you must remember to handle malformed input yourself. ## The three ResolverStyles `ResolverStyle` is an enum with three values; set it via `formatter.withResolverStyle(...)` (immutable copy-with). ### STRICT Validates **every** field against its real range *and* the calendar. - `2026-02-31` -> **throws** (no Feb 31). - `2026-13-01` -> throws (no month 13). - `24:00` -> throws. - Also strict about year/era: with patterns using `uuuu` (proleptic year) vs `yyyy` (year-of-era), STRICT may require an era field; many people switch year-of-era `yyyy` to year `uuuu` to parse cleanly under STRICT. ### SMART (the default) Accepts each field within its *general* range and makes sensible corrections without rolling into another unit. - `2026-02-31` -> **2026-02-28** (clamps the day to the last valid day of the month). - `2026-13-01` -> still throws (13 is outside the month range; SMART doesn't invent a 13th month). - This is the default precisely because it's forgiving of near-misses without producing surprising cross-unit jumps. ### LENIENT Treats out-of-range values as **overflow arithmetic** — it rolls over. - `2026-02-31` -> **2026-03-03** (28 days in Feb 2026, so 31 is 3 days past the end -> March 3). - `2026-13-01` -> **2027-01-01** (month 13 rolls to January of next year). - `25:00` time rolls into the next day. LENIENT almost never throws on range issues; it silently computes a different date. ## Why this matters The **danger** is silent transformation. Under SMART or LENIENT, a typo or malicious input like `2026-02-31` becomes a *valid* but *different* date with **no error**. For untrusted or important input (financial, security, scheduling), use **STRICT** so bad data fails loudly: ```java DateTimeFormatter strict = DateTimeFormatter .ofPattern("uuuu-MM-dd") .withResolverStyle(ResolverStyle.STRICT); LocalDate.parse("2026-02-31", strict); // throws DateTimeParseException ``` Note the use of `uuuu` (proleptic year, which directly carries the sign and needs no era) instead of `yyyy` (year-of-era) — a common STRICT gotcha. ## Handling the exception ```java try { LocalDate d = LocalDate.parse(userInput, strict); } catch (DateTimeParseException e) { // e.getParsedString() and e.getErrorIndex() help report where it failed reject("Invalid date: " + userInput); } ``` ## Summary table | Input | STRICT | SMART (default) | LENIENT | |---|---|---|---| | `2026-02-31` | throws | `2026-02-28` (clamp) | `2026-03-03` (roll) | | `2026-13-01` | throws | throws | `2027-01-01` (roll) | **Rule of thumb:** untrusted input -> STRICT + `uuuu`. Internal best-effort -> SMART. Almost never LENIENT unless you specifically want roll-over semantics.
- What does parsing "2026-02-31" produce under each resolver style?STRICT throws DateTimeParseException; SMART (default) clamps to 2026-02-28; LENIENT rolls over to 2026-03-03.
- Why switch yyyy to uuuu under STRICT?yyyy is year-of-era and STRICT can require an explicit era to disambiguate; uuuu is the proleptic (signed) year that needs no era, so it parses cleanly in strict mode.
saying these in an interview costs you the question
- Thinking parsing always throws on an invalid date (SMART/LENIENT silently fix or roll)
- Using SMART/LENIENT for untrusted input and getting a different valid date
- Believing DateTimeParseException is a checked exception
- Using yyyy with STRICT and being surprised it demands an era (use uuuu)
- Assuming LENIENT just relaxes formatting (it does overflow arithmetic)