Date Parsing

Su Suriya Ravichandran Updated 14 Nov 2026
5 min read ·Lesson 119 of 133

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:

StyleBehavior with 31/04/2026
SMART (default)Adjusts to a valid date: 2026-04-30
STRICTRejects: Invalid date 'APRIL 31'
LENIENTRolls 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.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Date Parsing and get answers drawn from it.

Signed-in readers only.