quarkusio/quarkus · error · java.lang.IllegalArgumentException

Not a formattable date/time object:

Error message

Not a formattable date/time object: 

What it means

The Qute time template extensions (formatDate/formatTime/formatDateTime via {#format} or time namespace) convert the given value before formatting. If the value is neither a temporal type (TemporalAccessor) nor a Number epoch-milli, the extension throws this IllegalArgumentException because it cannot render the object as a date/time.

Source

Thrown at extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/TimeTemplateExtensions.java:75

        return format(getFormattableObject(dateTimeObject, timeZone), pattern, locale, timeZone);
    }

    private static TemporalAccessor getFormattableObject(Object value,
            ZoneId timeZone) {
        if (value instanceof TemporalAccessor) {
            return (TemporalAccessor) value;
        } else if (value instanceof Date) {
            return LocalDateTime.ofInstant(((Date) value).toInstant(),
                    timeZone);
        } else if (value instanceof Calendar) {
            return LocalDateTime.ofInstant(((Calendar) value).toInstant(),
                    timeZone);
        } else if (value instanceof Number) {
            return LocalDateTime.ofInstant(
                    Instant.ofEpochMilli(((Number) value).longValue()),
                    timeZone);
        } else {
            throw new IllegalArgumentException("Not a formattable date/time object: " + value);
        }
    }

    private static DateTimeFormatter formatterForKey(Key key) {
        DateTimeFormatter formatter;
        DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
        builder.appendPattern(key.pattern);
        if (key.locale != null) {
            formatter = builder.toFormatter(key.locale);
        } else {
            formatter = builder.toFormatter();
        }
        return key.timeZone != null ? formatter.withZone(key.timeZone) : formatter;
    }

    static final class Key {

        private final String pattern;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Pass a supported type: TemporalAccessor (LocalDate/LocalDateTime/ZonedDateTime/Instant...) or a Number of epoch milliseconds.
  2. Parse String dates before rendering, or expose a getter returning a temporal type on the data model.
  3. Check for property-name typos so the correct date field is resolved.
  4. Note epoch values must be in milliseconds — convert seconds with Instant.ofEpochSecond(...) in your model.

Example fix

// before
data.put("created", "2024-01-01");
// after
data.put("created", LocalDate.of(2024, 1, 1)); // or Instant.ofEpochMilli(ms)
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(value instanceof TemporalAccessor) && !(value instanceof Number)) {
    throw new IllegalArgumentException("Value not formattable as date/time: " + value);
}

Type guard

boolean isFormattable(Object v) {
    return v instanceof TemporalAccessor || v instanceof Number;
}

Try / catch

try { engine.render(tpl, data); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("Not a formattable date/time object")) { /* fix model value type */ } else throw e; }

Prevention

When it happens

Trigger: Calling {#format myValue format="..."} (or time.format) with a value that is a String, Instant-like unsupported wrapper, null object, or arbitrary POJO instead of a date/time type or epoch millis Number.

Common situations: Passing a String date (e.g. "2024-01-01") from config/JSON without parsing it first; passing a wrong model property due to a name typo (resolves to a different bean); passing java.util.Date-like values that were converted or a Long representing seconds (not millis).

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/e01f6b88c9a3fa96. Report an issue: GitHub.