chinabugotech/hutool · error · IllegalArgumentException

Unterminated quote

Error message

Unterminated quote

What it means

Thrown when constructing a FastDateParser from a pattern that contains an opening single quote (') with no matching closing quote. In SimpleDateFormat-style patterns, a single quote begins a literal/quoted section that must be terminated by another single quote. The parser reaches end-of-pattern while the quote is still 'active', so the format object can never be built.

Source

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

		private StrategyAndWidth literal() {
			boolean activeQuote = false;

			final StringBuilder sb = new StringBuilder();
			while (currentIdx < pattern.length()) {
				final char c = pattern.charAt(currentIdx);
				if (!activeQuote && isFormatLetter(c)) {
					break;
				} else if (c == '\'' && (++currentIdx == pattern.length() || pattern.charAt(currentIdx) != '\'')) {
					activeQuote = !activeQuote;
					continue;
				}
				++currentIdx;
				sb.append(c);
			}

			if (activeQuote) {
				throw new IllegalArgumentException("Unterminated quote");
			}

			final String formatField = sb.toString();
			return new StrategyAndWidth(new CopyQuotedStrategy(formatField), formatField.length());
		}
	}

	private static boolean isFormatLetter(final char c) {
		return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
	}

	// Serializing
	// -----------------------------------------------------------------------

	/**
	 * Create the object after serialization. This implementation reinitializes the transient properties.
	 *
	 * @param in ObjectInputStream from which the object is being deserialized.

View on GitHub (pinned to 8870454b2a)

Solutions

  1. Balance the quotes: every opening ' must have a closing '. For a literal apostrophe inside the pattern use two consecutive quotes, e.g. "HH''mm".
  2. Validate the pattern before constructing the parser (unit test, or FastDateFormat.getInstance(pattern) in a try/catch at startup).
  3. If the quote is intentional literal text, ensure the literal segment is terminated, e.g. "yyyy'year'".

Example fix

// before
FastDateFormat fdf = FastDateFormat.getInstance("dd 'MM");
// after
FastDateFormat fdf = FastDateFormat.getInstance("dd 'MM'");
// or, for a literal apostrophe:
FastDateFormat fdf = FastDateFormat.getInstance("HH''mm");
Defensive patterns

Strategy: validation

Validate before calling

private static final Pattern BALANCED = Pattern.compile("^(?:[^']|'(?:[^']|'')*')*$");
void checkPattern(String p){
  // simple heuristic: count of unescaped quotes must be even
  int q=0; for(int i=0;i<p.length();i++){ if(p.charAt(i)=='\''){ if(i+1<p.length() && p.charAt(i+1)=='\''){ i++; continue; } q++; } }
  if((q&1)!=0) throw new IllegalArgumentException("unbalanced quote in pattern: "+p);
}

Try / catch

try { FastDateFormat fdf = FastDateFormat.getInstance(pattern); }
catch (IllegalArgumentException e) { if (e.getMessage().contains("Unterminated quote")) { /* fix pattern */ } else throw e; }

Prevention

When it happens

Trigger: Calling DateUtil.parse / new FastDateFormat (or DatePattern-based parsing) with a pattern like "dd 'MM" (only one quote), "HH''mm'ss" (unbalanced), or any pattern where a ' opens a literal that runs to the end of the string. Also triggered when a quote is meant to escape a format letter but is never closed.

Common situations: User-supplied or config-driven date patterns built by string concatenation; patterns localized from resource bundles that lose a quote during editing; patterns that attempt to embed an apostrophe (e.g. "o'clock") but only add one quote instead of two ('' for a literal apostrophe).

Related errors


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