Java SimpleDateFormat setLenient(false): Default Is Lenient

SimpleDateFormat is lenient by default, so parse() rolls invalid dates such as month 13 into another date. Call setLenient(false) for strict parsing, or use DateTimeFormatter with ResolverStyle.STRICT and the “uuuu” pattern in java.time.

By default, SimpleDateFormat is lenient (the method isLenient() returns true), so parse() accepts out-of-range values such as month 13 or February 30 and rolls them over into another date instead of throwing a ParseException. The DateFormat Javadoc) states it in one line, “By default, parsing is lenient”, and calling setLenient(false)) switches the parser to strict mode, where the input must match the pattern.

We turn off leniency whenever we use parse() to validate dates typed by users or read from files, such as a birth date in a sign-up form or a date column in a CSV import. In new code, java.time does the same job with ResolverStyle.STRICT.

The following example parses the invalid date “13/01/2024” with the pattern “MM/dd/yyyy”, first in the default lenient mode and then in strict mode, and shows the java.time alternative (Java 25).

// 1. SimpleDateFormat is lenient by default
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
boolean lenient = sdf.isLenient();                    // true
Date rolled = sdf.parse("13/01/2024");                // Wed Jan 01 2025 (month 13 rolls over)

// 2. Strict parsing
sdf.setLenient(false);
Date strict = sdf.parse("13/01/2024");                // ParseException: Unparseable date: "13/01/2024"

// 3. java.time alternative: ResolverStyle.STRICT with "uuuu"
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/uuuu")
        .withResolverStyle(ResolverStyle.STRICT);
LocalDate leapDay = LocalDate.parse("02/29/2024", formatter);   // 2024-02-29
LocalDate invalid = LocalDate.parse("02/30/2024", formatter);   // DateTimeParseException: Invalid date 'FEBRUARY 30'

Notice that the strict DateTimeFormatter uses “uuuu” (year) instead of “yyyy” (year of era), because ResolverStyle.STRICT cannot build a LocalDate from a year of era without an era field. We cover the reusable validation method in section 4.

In the next sections, we look at why the lenient parser accepts a wrong date and at the Calendar leniency rules that SimpleDateFormat inherits, and then build a strict validator with java.time.

1. Java Date Validation with SimpleDateFormat.parse()

Many developers use the SimpleDateFormat.parse() method for Java date validation. If the parse() method is able to parse the date, then it is considered that the input is valid. Well, it might be incorrect.

String datePattern = "MM/dd/yyyy";
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);

//Lenient conversion result in unexpected output
Date d = sdf.parse("2012/12/17");
System.out.println(d);

The program prints a date in the year 184 instead of failing.

Wed Aug 12 00:00:00 IST 184

1.1. What went wrong?

The above date validation is strange for two reasons. First, it should have flagged the validation error, and second, the date object obtained is completely useless.

Well, the error is in parsing logic. The parse() method uses the positions of pattern keywords in the date pattern and uses them to parse the input string. It is not intelligent by default to use the right characters for parsing, and it uses what comes in its way (even slashes). The lenient calendar then turns month 2012 and the other out-of-range fields into a valid but unrelated date.

2. Correct way to validate a date with SimpleDateFormat.setLenient()

The solution is to use the SimpleDateFormat.setLenient() method to bring the missing intelligence. With setLenient(false), the parser throws a ParseException for any input that does not match the pattern or has an out-of-range field.

String datePattern = "MM/dd/yyyy";
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);

sdf.setLenient(false);

//Strict parsing throws ParseException
Date d = sdf.parse("2012/12/17");
System.out.println(d);

The program fails with the following exception.

java.text.ParseException: Unparseable date: "2012/12/17"
	at java.base/java.text.DateFormat.parse(DateFormat.java:427)
	...

So, clearly setting setLenient(false) corrects the parsing behavior of SimpleDateFormat.

We can verify the correct parsing by passing a valid date in the required pattern.

String datePattern = "MM/dd/yyyy";
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);

sdf.setLenient(false);

//Strict parsing accepts a date that matches the pattern
Date d = sdf.parse("12/17/2012");
System.out.println(d);

The valid date parses without an error.

Mon Dec 17 00:00:00 IST 2012

3. Default leniency behavior in Calendar class

SimpleDateFormat computes the date with a Calendar, and DateFormat.setLenient() calls getCalendar().setLenient(), so the Calendar leniency rules decide the parse result.

The Calendar class has two modes for interpreting the calendar fields, lenient and non-lenient. When a calendar is in lenient mode, it accepts a wider range of calendar field values than it produces. When a calendar recomputes field values for return by get(), all of the calendar fields are normalized.

For example, a lenient GregorianCalendar interprets “MONTH = JANUARY and DAY_OF_MONTH = 32” as February 1, which is rarely what a date validation wants.

When a Calendar is in non-lenient mode, it throws an exception if there is any inconsistency in its calendar fields. For example, a GregorianCalendar always produces DAY_OF_MONTH values between 1 and the length of the month. A non-lenient GregorianCalendar throws an exception upon calculating its time or calendar field values if any out-of-range field value has been set.

Default is lenient mode.

4. Strict Date Validation with java.time

Since Java 8, DateTimeFormatter in the java.time.format package is the recommended replacement for SimpleDateFormat, because it is immutable and thread-safe. It is not fully strict by default either. The default ResolverStyle.SMART changes “02/30/2024” to 2024-02-29, so we set ResolverStyle.STRICT for validation.

The following method returns false for null, blank or invalid input instead of throwing an exception, so we can call it on raw form field values.

static final DateTimeFormatter STRICT_FORMATTER =
    DateTimeFormatter.ofPattern("MM/dd/uuuu").withResolverStyle(ResolverStyle.STRICT);

static boolean isValidDate(String text) {
  if (text == null) {
    return false;
  }
  try {
    LocalDate.parse(text.strip(), STRICT_FORMATTER);
    return true;
  } catch (DateTimeParseException e) {
    return false;
  }
}

boolean leapDay = isValidDate("02/29/2024");     // true
boolean feb30 = isValidDate("02/30/2024");       // false
boolean wrongOrder = isValidDate("2012/12/17");  // false
boolean missing = isValidDate(null);             // false

With “yyyy” in the pattern, the same strict formatter rejects every date, even “02/28/2024”, with the message “Unable to obtain LocalDate from TemporalAccessor”. So a strict pattern needs “uuuu”, or “yyyy” plus the era letter “G”.

5. Conclusion

In this Java date validation example, we learned that SimpleDateFormat parses leniently by default, and that setLenient(false) makes it accept only the dates that match the pattern and fail for other date patterns.

For new code, a DateTimeFormatter with ResolverStyle.STRICT and the “uuuu” year pattern gives the same strict validation without the thread-safety problems of SimpleDateFormat.

6. References

Happy Learning !!

Leave a Comment

  1. Wow, thank you !!!! didnt find answer on stackoverflow, most of them were saying don’t use SDF api, but this solution saved me :)

  2. Hi, I’m getting below validation error while unmarshaling my xml file

    cvc-datatype-valid.1.2.1: ‘2019-02-19T14:34:40+03’ is not a valid value for ‘dateTime’.

    Please help me for resolve it.

    Thanks,
    Pawan

  3. Even if you use setLenient to FALSE, in the cases that the intervals are acceptal but the format is not, the parse will result in success.
    To avoid it, use format over the parse result Date and compare with “equals” the initial String date.

  4. I tried to parse this date “2014-100-11 22:53:45” and guess what it parsed!!!!….no error!!!!

    and also sometimes my simpledateformatter returns date in format like ‘2014-0011-20 21:53:45 ‘. How it can be?

  5. Why the default for setLenient was not false instead of true is beyond me.
    Java designers smokin’ crack again…

  6. Hi Lokesh,
    JAXB is not populating the dateTime fields with joda DateTime values
    @XmlElement(name=”joindate”)
    private DateTime joinDate;

    I don’t see any error in the log as well because it is generating as empty tag.

  7. thanks a lot buddy…
    i’ve wasted nearly half day to find the method ‘setLenient(false)’.

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.