Date Parsing
Parsing converts text into a date or time value: turning "13/11/2026" from a form, "2026-11-13T09:30:15" from a JSON payload, or "13 Nov 2026" from a spreadsheet into a LocalDate, LocalDateTime, or another java.time type. Parsing is the reverse of formatting and uses the same DateTimeFormatter, but it needs more care: input comes from people and other systems, and it can be in an unexpected format, ambiguous, or simply invalid.
Parsing ISO-8601 Text
Every java.time type has a static parse method that accepts its standard ISO form without any formatter:
LocalDate.parse("2026-11-13"); // 2026-11-13
LocalTime.parse("09:30"); // 09:30
LocalDateTime.parse("2026-11-13T09:30:15"); // 2026-11-13T09:30:15
ZonedDateTime.parse("2026-11-13T09:30+05:30[Asia/Kolkata]"); // with zone
OffsetDateTime.parse("2026-11-13T09:30:00+05:30");
Instant.parse("2026-11-13T04:00:00Z");
ISO-8601 is the format used by JSON APIs, HTML date inputs (<input type="date"> submits 2026-11-13), databases, and log files. Whenever you control both ends of an exchange, use ISO formats so that no custom parsing is needed.
Parsing with a Pattern
For other formats, pass a formatter whose pattern matches the input exactly:
LocalDate d = LocalDate.parse("13/11/2026", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
LocalDateTime dt = LocalDateTime.parse("13-11-2026 06:45 PM",
DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a", Locale.ENGLISH)); // 2026-11-13T18:45
Pattern letters have the same meanings as in formatting (see the Date Formatting lesson). Text fields such as MMM (month names) and a (AM/PM) depend on the locale, so pass one explicitly: "Nov" parses with Locale.ENGLISH but not with a German default locale, which expects "Nov.".
When Parsing Fails
Input that does not match throws DateTimeParseException, a subclass of DateTimeException:
try {
LocalDate.parse("13/11/2026"); // ISO expects yyyy-MM-dd
} catch (DateTimeParseException e) {
System.out.println(e.getMessage());
// Text '13/11/2026' could not be parsed at index 0
System.out.println(e.getErrorIndex()); // 0
System.out.println(e.getParsedString()); // 13/11/2026
}
getErrorIndex() reports the position where parsing failed, useful for error messages that point to the problem. Wrap every parse of external input in a try/catch (or a helper returning Optional) so that one malformed value does not crash a request or an import job.
Resolver Styles: Catching Impossible Dates
A pattern can match the shape of the text while the values are impossible, such as 31 April. How the formatter handles that depends on its resolver style:
| Style | Behavior with 31/04/2026 |
|---|---|
SMART (default) | Adjusts to a valid date: 2026-04-30 |
STRICT | Rejects: Invalid date 'APRIL 31' |
LENIENT | Rolls over: 2026-05-01 |
LocalDate.parse("31/04/2026", DateTimeFormatter.ofPattern("dd/MM/yyyy")); // 2026-04-30, silently!
The default SMART style quietly changes invalid days of the month to the last valid day. For user input, a typo becoming a different date is usually worse than an error. Use STRICT when validating input.
STRICT requires uuuu, not yyyy
With STRICT, the year pattern must be uuuu:
DateTimeFormatter.ofPattern("dd/MM/yyyy").withResolverStyle(ResolverStyle.STRICT);
// parsing "13/11/2026" fails: Unable to obtain LocalDate from TemporalAccessor ...
DateTimeFormatter.ofPattern("dd/MM/uuuu").withResolverStyle(ResolverStyle.STRICT);
// parsing "13/11/2026" → 2026-11-13; "31/04/2026" → DateTimeParseException
yyyy means year of era, which in strict mode also requires an era (AD/BC) to be present in the input. uuuu means the proleptic year, a plain year number, which is what dates almost always contain. In patterns for strict parsing, always use uuuu.
Case-Insensitive and Flexible Parsing
Parsing is case-sensitive by default, so "13 nov 2026" fails against d MMM yyyy. DateTimeFormatterBuilder creates formatters with extra options:
DateTimeFormatter f = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("d MMM uuuu")
.toFormatter(Locale.ENGLISH)
.withResolverStyle(ResolverStyle.STRICT);
LocalDate.parse("13 nov 2026", f); // 2026-11-13
The builder also supports optional sections ([...] in patterns, such as "yyyy-MM-dd[ HH:mm]"), default values for missing fields (parseDefaulting), and lenient handling of whitespace.
Accepting Several Formats
When input may arrive in several known formats, try each formatter in turn:
static Optional<LocalDate> parseAny(String text, List<DateTimeFormatter> formats) {
for (DateTimeFormatter f : formats) {
try {
return Optional.of(LocalDate.parse(text.strip(), f));
} catch (DateTimeParseException ignored) {
}
}
return Optional.empty();
}
List the formats in a deliberate order, and never include two that can read the same text differently. dd/MM/yyyy and MM/dd/yyyy both accept 03/04/2026, as 3 April and 4 March respectively. Since day-first is the norm in India and most of the world, and month-first in the United States, a system must choose one convention per input source rather than guessing.
Parsing Dates with Times and Zones
ZonedDateTime z = ZonedDateTime.parse("2026-11-13 09:30 Asia/Kolkata",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm VV"));
LocalDateTime local = LocalDateTime.parse("2026-11-13 09:30",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
ZonedDateTime withZone = local.atZone(ZoneId.of("Asia/Kolkata")); // attach a known zone afterwards
If text contains no zone or offset, parse it as a LocalDateTime and attach the zone that the source is known to use. Never assume the server's default zone for data from users in other places.
Parsing Versus Validation
Successful parsing proves only that the text is a real date. Business rules still need checking:
LocalDate dob = LocalDate.parse(input, STRICT_FORMAT);
if (dob.isAfter(LocalDate.now())) {
throw new IllegalArgumentException("Date of birth cannot be in the future");
}
Practical Example: Importing Dates from Mixed Sources
A student-import job receives enrollment dates from several sources: an API (ISO), an Indian spreadsheet (dd/MM/yyyy or dd-MM-yyyy), and manually typed text (13 Nov 2026, in any letter case). Impossible dates and unrecognized formats must be rejected, never silently changed.
import java.time.*;
import java.time.format.*;
import java.util.*;
public class DP {
static final List<DateTimeFormatter> ACCEPTED = List.of(
DateTimeFormatter.ISO_LOCAL_DATE, // 2026-11-13
DateTimeFormatter.ofPattern("dd/MM/uuuu").withResolverStyle(ResolverStyle.STRICT), // 13/11/2026
DateTimeFormatter.ofPattern("dd-MM-uuuu").withResolverStyle(ResolverStyle.STRICT), // 13-11-2026
new DateTimeFormatterBuilder().parseCaseInsensitive()
.appendPattern("d MMM uuuu").toFormatter(Locale.ENGLISH)
.withResolverStyle(ResolverStyle.STRICT) // 13 Nov 2026, 13 nov 2026
);
static Optional<LocalDate> parseFlexible(String raw) {
String text = raw.strip();
for (DateTimeFormatter f : ACCEPTED) {
try {
return Optional.of(LocalDate.parse(text, f));
} catch (DateTimeParseException ignored) {
}
}
return Optional.empty();
}
public static void main(String[] args) {
for (String s : List.of("2026-11-13", " 13/11/2026 ", "13-11-2026", "13 nov 2026", "29/02/2026", "11/13/2026", "tomorrow")) {
System.out.printf("%-14s -> %s%n", "\"" + s + "\"", parseFlexible(s).map(LocalDate::toString).orElse("invalid"));
}
}
}
Output:
"2026-11-13" -> 2026-11-13
" 13/11/2026 " -> 2026-11-13
"13-11-2026" -> 2026-11-13
"13 nov 2026" -> 2026-11-13
"29/02/2026" -> invalid
"11/13/2026" -> invalid
"tomorrow" -> invalid
Four different spellings of the same date all produce 2026-11-13. The input is stripped first, since formatters do not ignore surrounding spaces. ISO_LOCAL_DATE is strict by definition; every custom formatter uses uuuu with ResolverStyle.STRICT, so 29 February 2026 (not a leap year) is rejected rather than turned into the 28th. The US-style "11/13/2026" is rejected too: with the accepted day-first patterns, it would mean month 13, which does not exist. The builder-made formatter accepts month names in any letter case. Because parseFlexible returns Optional, the caller decides what to do with rejected rows, typically writing them to an error report for a person to correct.