Skip to content

ZonedDateTime not serialised as UTC by default altough ObjectMapper Javadocs claiming UTC is the default #303

Description

@Robsmon

As stated in the Jackson ObjectMapper the default should be UTC for writing Json:

/**
 * Method for overriding default TimeZone to use for formatting.
 * Default value used is UTC (NOT default TimeZone of JVM).
 */

public ObjectMapper setTimeZone(TimeZone tz) {
    _deserializationConfig = _deserializationConfig.with(tz);
    _serializationConfig = _serializationConfig.with(tz);
    return this;
}

https://github.com/FasterXML/jackson-databind/blob/b8910125bba81862ea278821ccfd198b1f69fd5d/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java#L2529-L2537

/**
 * Base settings contain defaults used for all {@link ObjectMapper}
 * instances.
 */
protected final static BaseSettings DEFAULT_BASE = new BaseSettings(
        null, // cannot share global ClassIntrospector any more (2.5+)
        DEFAULT_ANNOTATION_INTROSPECTOR,
         null, TypeFactory.defaultInstance(),
        null, StdDateFormat.instance, null,
        Locale.getDefault(),
        null, // to indicate "use Jackson default TimeZone" (UTC since Jackson 2.7)
        Base64Variants.getDefaultVariant(),
        // Only for 2.x; 3.x will use more restrictive default
        LaissezFaireSubTypeValidator.instance,
        // Since 2.12:
        new DefaultAccessorNamingStrategy.Provider(),
        // Since 2.16: [databind#2502] Add a way to configure Caches Jackson uses
        DefaultCacheProvider.defaultInstance()
);

https://github.com/FasterXML/jackson-databind/blob/b8910125bba81862ea278821ccfd198b1f69fd5d/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java#L408-L422

With the java 8 time module when the timezone is set to null no time zone information is used on serialisation as the provider.getConfig().hasExplicitTimeZone() does result in false if the default of null is set. So ZonedDateTime is not written with utc as default.

// @since 2.12
protected String formatValue(T value, SerializerProvider provider)
{
DateTimeFormatter formatter = (_formatter != null) ? _formatter : defaultFormat;
if (formatter != null) {
if (formatter.getZone() == null) { // timezone set if annotated on property
// If the user specified to use the context TimeZone explicitly, and the formatter provided doesn't contain a TZ
// Then we use the TZ specified in the objectMapper
if (provider.getConfig().hasExplicitTimeZone() && provider.isEnabled(WRITE_DATES_WITH_CONTEXT_TIME_ZONE)) {
formatter = formatter.withZone(provider.getTimeZone().toZoneId());
}
}
return formatter.format(value);
}
return value.toString();
}

Is this expected behavior to not overwrite the TimeZone defined in the ZonedDateTime? Than this should be properly Documented.

Minimal example:

    ObjectMapper testmapper = new ObjectMapper();
    testmapper.registerModule(new JavaTimeModule());
    testmapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    ZonedDateTime testtime = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));
    String teststring = testmapper.writeValueAsString(testtime);
    // 2024-02-20T11:37:42.009045+01:00

    testmapper = new ObjectMapper();
    testmapper.registerModule(new JavaTimeModule());
    testmapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    testmapper.setTimeZone(TimeZone.getTimeZone("UTC"));
    testtime = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));
    teststring = testmapper.writeValueAsString(testtime);
    // 2024-02-20T10:38:13.0324177Z

Activity

  1. cowtowncoder commented on Feb 28, 2024

    @cowtowncoder
    Member

    Quick note: I think (but cannot canonically claim as the official answer) that the default TimeZone is only meant to be used in absence of explicit one; at least with default settings.
    However. Since you mention WRITE_DATES_WITH_CONTEXT_TIME_ZONE, enabling that... oh.
    It does look like it should be enabled since 2.13.

    (as the background this default timezone was most useful for java.util.Date, instances of which do not have timezone specified and must use something if serialized as String value).

    So maybe this is indeed working incorrectly. But aside from fixing this (and testing properly), there is then the question of backwards-compatibility -- I think this might be acceptable for a minor version (even if others might argue otherwise). But would need to be considered carefully.

  2. cowtowncoder commented on Mar 26, 2025

    @cowtowncoder
    Member

    --Ok, yes, I think behavior is correct: ZonedDateTime's Time zone should not be overwritten by default.--

    EDIT: I agree this is confusing; although default is UTC, that is not taken into account in this particular case, due to default timezone being implicit, not explicit.

    But due to backwards-compatibility aspects, I think behavior needs to remain as-is. I am open to suggestions on how to change Javadocs.

  3. changed the title [-]ZonedDateTime not serialised as UTC by default[/-] [+]`ZonedDateTime` not serialised as UTC by default altough ObjectMapper Javadocs claiming UTC is the default[/+] on Nov 18, 2025
  4. added
    documentationIssue that documentation improvements could solve or alleviate
    on Nov 18, 2025
  5. AzazelSensei commented on Sep 21, 2026

    @AzazelSensei
    Contributor

    Opened #390 with the javadoc/README note on implicit UTC vs ZonedDateTime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationIssue that documentation improvements could solve or alleviate

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions