chinabugotech/hutool · error · ParseException

(The {} locale does not support dates before 1868 AD) Unpars

Error message

(The {} locale does not support dates before 1868 AD)
Unparseable date: "{}

What it means

Thrown by FastDateParser.parse(String) when the Japanese Imperial calendar locale (ja_JP_JP / Locale with JAPANESE_IMPERIAL calendar) is in use and the date being parsed resolves to a year before 1868 AD, which precedes the Meiji era and has no representation in that calendar system. It wraps the generic 'Unparseable date' with a locale-specific note.

Source

Thrown at hutool-core/src/main/java/cn/hutool/core/date/format/FastDateParser.java:219

	 * @param in ObjectInputStream from which the object is being deserialized.
	 * @throws IOException            if there is an IO issue.
	 * @throws ClassNotFoundException if a class cannot be found.
	 */
	private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
		in.defaultReadObject();

		final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
		init(definingCalendar);
	}

	@Override
	public Date parse(String source) throws ParseException {
		final ParsePosition pp = new ParsePosition(0);
		final Date date = parse(source, pp);
		if (date == null) {
			// Add a note re supported date range
			if (locale.equals(JAPANESE_IMPERIAL)) {
				throw new ParseException("(The " + locale + " locale does not support dates before 1868 AD)\n" +
						"Unparseable date: \"" + source, pp.getErrorIndex());
			}
			throw new ParseException("Unparseable date: " + source, pp.getErrorIndex());
		}
		return date;
	}

	@Override
	public Date parse(String source, ParsePosition pos) {
		// timing tests indicate getting new instance is 19% faster than cloning
		final Calendar cal = Calendar.getInstance(timeZone, locale);
		cal.clear();

		return parse(source, pos, cal) ? cal.getTime() : null;
	}

	@Override
	public boolean parse(String source, ParsePosition pos, Calendar calendar) {

View on GitHub (pinned to 8870454b2a)

Solutions

  1. Parse with a Gregorian/proleptic locale (e.g. Locale.US or Locale.JAPAN) instead of the imperial Japanese locale.
  2. Filter or reject dates before 1868 before attempting parse in the imperial locale.
  3. Set the JVM default locale away from ja_JP_JP if it was inherited unintentionally.

Example fix

// before
FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd", tz, new Locale("ja","JP","JP"));
fdf.parse("1800-01-01");
// after
FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd", tz, Locale.US);
fdf.parse("1800-01-01");
Defensive patterns

Strategy: validation

Validate before calling

boolean isImperial(Locale l){ return l!=null && "JP".equals(l.getCountry()) && "ja".equals(l.getLanguage()) && "JP".equals(l.getVariant()); }
// if isImperial(locale) && parsedYear<1868 -> use a Gregorian locale

Try / catch

try { return fdf.parse(source); }
catch (ParseException e) { if (e.getMessage().contains("does not support dates before 1868")) { return gregorianFdf.parse(source); } throw e; }

Prevention

When it happens

Trigger: Parsing a string such as '1800-01-01' with a FastDateFormat configured for Locale.JAPANESE_IMPERIAL (typically Locale = new Locale("ja","JP","JP") which triggers the Japanese Imperial calendar). Any parsed year < 1868 triggers it, even if the pattern itself matches.

Common situations: Server/app default Locale set to Japanese Imperial via config or container locale negotiation; legacy data containing historical dates being parsed in a locale-aware component; tests run on a JVM whose default locale resolves to the imperial calendar.

Related errors


AI-assisted analysis of chinabugotech/hutool@8870454b2a (2026-08-14). Data as JSON: /api/errors/4c0c5e3fae448779. Report an issue: GitHub.