Serguey Asael Shinder: DateTimeFormatter.ofPattern resolves smartly, so 2026-02-30 parses as February 28
Two lines that look interchangeable behave differently on bad input:
LocalDate.parse("2026-02-30"); // DateTimeParseException
LocalDate.parse("2026-02-30", DateTimeFormatter.ofPattern("uuuu-MM-dd")); // 2026-02-28
The difference is the resolver style. Parsing happens in two phases: the text is first split into field values, then those values are resolved into a date, and ResolverStyle controls the second phase. The javadoc of DateTimeFormatter.ISO_LOCAL_DATE, which LocalDate.parse(CharSequence) uses, says it "uses the STRICT resolver style". The javadoc of ofPattern(String) says "It uses SMART resolver style". (https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/format/DateTimeFormatter.html)
What SMART does with a day-of-month is spelled out in the enum itself: in the ISO calendar, smart mode "will ensure that the day-of-month is from 1 to 31, converting any value beyond the last valid day-of-month to be the last valid day-of-month." STRICT, by contrast, "will ensure that the day-of-month is valid for the year-month, rejecting invalid values." (https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/format/ResolverStyle.html) So 30 February passes the 1-to-31 check and is moved to the 28th; 32 February would still fail.

This matters wherever a date is a user's claim rather than a value you produced: a birth date on a form, an expiry date in an import file, a booking date in an API request. With the pattern formatter, a typo that should have been rejected becomes a real, plausible date, and nothing downstream can tell. The same applies to 31 April, 31 June, 31 September and 31 November, and to 29 February in a non-leap year.
If you need a custom pattern and want impossible dates rejected, ask for it explicitly:
DateTimeFormatter f = DateTimeFormatter.ofPattern("uuuu-MM-dd")
.withResolverStyle(ResolverStyle.STRICT);
Two habits help. Write a test with an impossible date for every parser that reads external input, because the happy path looks the same under both styles. And when a formatter is shared as a constant, put the resolver style in the declaration, so the next reader does not have to remember which factory method picked which default.