`WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS`/`READ_DATE_TIMESTAMPS_AS_NANOSECONDS` only work on numeric timestamps
Hello.
I'm using Spring Boot 3.5.6 with Java 25 and I need a way to have Jackson to serialize/deserialize date-time values up to milliseconds precision and not higher (nanoseconds, actually).
I tried setting write-date-timestamps-as-nanoseconds and read-date-timestamps-as-nanoseconds to false, but it looks like they're simply ignored if I don't have write-dates-as-timestamps to true.
Indeed, I would like to have my JSON contain a date-time in the ISO standard format and not as a long value, but I wasn't able to find anything in the documentation for this.
I wonder why such a feature is provided only for numeric timestamps. It should work for both IMHO.
Below a small test case to reproduce the issue:
@SpringBootTest(
webEnvironment = MOCK,
properties = {
//"spring.jackson.serialization.write-dates-as-timestamps=true",
"spring.jackson.serialization.write-date-timestamps-as-nanoseconds=false",
"spring.jackson.deserialization.read-date-timestamps-as-nanoseconds=false"
}
)
public class ObjectMapperTest {
@Autowired
private ObjectMapper objectMapper;
@Test
public void shouldHandleDateTimeUpToMillis() throws JsonProcessingException {
// given
Instant instant = Instant.now().with(NANO_OF_SECOND, 1);
assertThat(instant.getNano()).isNotZero();
// when
String serialized = objectMapper.writeValueAsString(instant);
// then
Instant deserialized = objectMapper.readValue(serialized, Instant.class);
assertThat(deserialized).isEqualTo(instant.truncatedTo(MILLIS));
}
}
Hmmm, first of all, it would be good to have Jackson-only configuration & reproduction, so we know exactly how the Mapper is configured and work.
Anyway, if there isn't a feature to serialize-deserialize the way you want, you can always create a custom serializer, deserializer.
Of course I could create a custom serializer and deserializer for this, but it would be just a workaround and it won't really solve the fact that the library is inconsistent in what it provides given that it provides this feature already for numerical timestamps.
Here I'm asking to fix such inconsistency.
Hmmm, first of all, it would be good to have Jackson-only configuration & reproduction, so we know exactly how the Mapper is configured and work.
I'll try to provide one as soon as I've time.
As you already seen comment in similar issue, you know inconsistency is made in JDK and I think what you are asking here is feature to mitigate that inconsistency?
Yes, with no Spring, just Jackson configuration and JUnit test. Couple of comments on the test code describing what should pass and not will be great!
No, I'm asking to get the inconsistency in the library to be fixed.
I'm not asking for interoperability between Java < 9 and the rest here, but to have the same feature flags available already for numerical timestamps also for non-numerical ones.
In the test above, if I uncomment the feature flag to write date-times as numerical timestamps (overriding therefore the settings from Spring Boot which uses non-numerical timestamps), the test will pass. On the other end, if I keep that line commented out, the test fails.
This shows that the library is inconsistent, since the JDK is the same for both the executions.
This shows that the library is inconsistent, since the JDK is the same for both the executions.
Oh okay then let's first have reproduction without Spring, so we can pin-down the inconsistency being created here.
Kind of the same here https://github.com/FasterXML/jackson-modules-java8/issues/374
I thought you meant "same" here :-0
This shows that the library is inconsistent, since the JDK is the same for both the executions.
Oh okay then let's first have reproduction without Spring, so we can pin-down the inconsistency being created here.
Kind of the same here #374
I thought you meant "same" here :-0
"Kind of" the same, because if those feature flags would be consistent they would not have the issue either. They could then, as well, disable the nanoseconds resolution and be fine (I think).
@JooHyukKim here a test with just ObjectMapper:
public class ObjectMapperTest {
@Test
public void shouldAllowMillisPrecision() throws JsonProcessingException {
// given
Instant instant = Instant.now().with(NANO_OF_SECOND, 1);
Instant expected = instant.truncatedTo(MILLIS);
ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // Commenting this out makes the test pass
om.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
om.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
// when
String serialized = om.writeValueAsString(instant);
Instant deserialized = om.readValue(serialized, Instant.class);
// then
assertThat(deserialized).isEqualTo(expected);
}
}
Jackson version? (specifically, 2.20.0 or 3.0.0 ?)
Jackson version? (specifically, 2.20.0 or 3.0.0 ?)
It's 2.19.2 in the BoM imported by Spring Boot 3.5.6.
Commenting out or not, is too naive for a hint.
So when
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is....
- enabled, the instant is serialized as "1760200459000", and this would pass, because affected by
READ/WRITE_DATE_TIMESTAMPS_AS_NANOSECONDSfeatures - disabled, the instant is serialized as "2025-10-11T16:27:19.000000001Z", would fail the test, because you are testing the deserialized with MANUALLY trunclated textual timestamp.
@cdprete I don't think there could be any symmetry between the two
- Correctly desrialized with nanosecond 1, from "2025-10-11T16:27:19.000000001Z", and
- MANUALLY TRUNCATED nanosecond "2025-10-11T16:27:19Z"
If you test assertEquals(deserialized, instant); not with expected, would pass already.
Commenting out or not, is too naive for a hint.
So when
SerializationFeature.WRITE_DATES_AS_TIMESTAMPSis....
- enabled, the instant is serialized as "1760200459000", and this would pass, because affected by
READ/WRITE_DATE_TIMESTAMPS_AS_NANOSECONDSfeatures- disabled, the instant is serialized as "2025-10-11T16:27:19.000000001Z", would fail the test, because you are testing the deserialized with MANUALLY trunclated textual timestamp.
@cdprete I don't think there could be any symmetry between the two
- Correctly desrialized with nanosecond 1, from "2025-10-11T16:27:19.000000001Z", and
- MANUALLY TRUNCATED nanosecond "2025-10-11T16:27:19Z"
If you test
assertEquals(deserialized, instant);not withexpected, would pass already.
Commenting out or not, is too naive for a hint.
That's all what takes to show the inconsistency.
With READ/WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS you have the library being able to handle a FULL Instant and reading/writing it then up to the milliseconds. But only for WRITE_DATES_AS_TIMESTAMPS date-times.
disabled, the instant is serialized as "2025-10-11T16:27:19.000000001Z", would fail the test, because you are testing the deserialized with MANUALLY trunclated textual timestamp.
That's what I'm saying since the beginning and the test wanted to prove it. Those flags are simply being ignored for NON-NUMERICAL timestamps. Therefore, the library is inconsistent.
I see 2 ways to fix this:
- you offer a new pair of flags for non-numerical timestamps which carry on the same behavior of the existing ones
- you still offer a new pair of flags (or reuse the existing ones) which would be applied to both the representations and deprecate the existing ones.
Quick note: as Javadocs for SerializationFeature say:
/**
* Feature that controls whether numeric timestamp values are
* to be written using nanosecond timestamps (enabled) or not (disabled);
...
*/
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS(true),
(and same for matching DeserializationFeature) this only applies to numeric timestamps and not to textual ISO-8601 serialization.
So behavior is as documented -- and feature name itself suggests it applies to TIMESTAMPS -- same for Spring properties.
So what you are asking, I think, is to allow something similar for ISO-8601 serializations.
This would be a new feature, not a bug (just wrt issue title)
But given that Jackson 3.0.0 was just released and the plan is to move 2.x more to bug-fix mode, I think such new functionality would need to be for Jackson 3.x.
If so, we could, perhaps consider new DateTimeFeature flags.
Although I am not quite if simple flags are the way to do it.
Also: wouldn't @JsonFormat configuration allow specifying desired DateTimeFormat for only including milliseconds in serialization?
Maybe solution is as simple as that.
Fwtw, it is possible to both use annotation
class Value {
@JsonFormat(pattern = "yyyy-MM-dd'X'HH:mm")
public LocalDateTime dt;
}
and set defaults for various types:
ObjectMapper m = mapperBuilder().withConfigOverride(LocalDateTime.class,
cfg -> cfg.setFormat(JsonFormat.Value.forPattern("yyyy-MM-dd'X'HH:mm")))
.build();
(obv pattern here wouldn't be one to use, and type would be something other than LDT -- I just picked examples from module unit tests).
Hope this helps!
Quick note: as Javadocs for
SerializationFeaturesay:/** * Feature that controls whether numeric timestamp values are * to be written using nanosecond timestamps (enabled) or not (disabled); ... */ WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS(true),(and same for matching
DeserializationFeature) this only applies to numeric timestamps and not to textual ISO-8601 serialization.So behavior is as documented -- and feature name itself suggests it applies to TIMESTAMPS -- same for Spring properties.
So what you are asking, I think, is to allow something similar for ISO-8601 serializations.
This would be a new feature, not a bug (just wrt issue title) But given that Jackson 3.0.0 was just released and the plan is to move 2.x more to bug-fix mode, I think such new functionality would need to be for Jackson 3.x. If so, we could, perhaps consider new
DateTimeFeatureflags.Although I am not quite if simple flags are the way to do it.
Also: wouldn't
@JsonFormatconfiguration allow specifying desiredDateTimeFormatfor only including milliseconds in serialization? Maybe solution is as simple as that.
I know what the Javadoc says. What you see as a feature, I see it as a bug since now the library is inconsistent. If a feature X is available for one format it should then be available also for the other one, unless a meaningful motivation exists (here there is none).
@JsonFormat and other solutions would just be workaround to push the resolution of the bug to consumers of the library instead of fixing it here.
Personally, I'm reluctant to have to patch all my DTOs and to have to maintain those patches just because the bug won't be fixed - or even admitted I would say - at the right place.
Hmmmm, I don't see much value in arguing over what we call it. You are free to call it either bug/feature.
But either way, this is something that was never asked before and there needs to be implementation. At the moment I am occupied on other things. Would you be willing to contribute @cdprete? Regarding, "how" to implement, there is (very simple) coding guideline you can follow.
But either way, this is something that was never asked before and there needs to be implementation.
So, you don't see the current feature as an half-baked one? Moreover, regarding the "it was never asked", there are some other tickets complaining kind of the same issue and that they would not be there if the feature would have been properly implemented by taking into account both the formats.
Would you be willing to contribute?
I potentially could, assuming I can find some time (I'm also busy 😉), if some code-style descriptor for ImtelliJ is provided that automatically reformats the code according to your standards.
So, you don't see the current feature as an half-baked one? Moreover, regarding the "it was never asked", there are some other tickets complaining kind of the same issue and that they would not be there if the feature would have been properly implemented by taking into account both the formats.
I don't know what type of software engineering career you had, but I am very surprised that you expect everything to be in perfect, ideal form, without talking about priorities, and history.
not be there if the feature would have been properly implemented by taking into account both the formats.
Also, "properly" makes me wonder how many lines of code have you helped out to shape the library you are using now?
I don't know what type of software engineering career you had, but I am very surprised that you expect everything to be in perfect, ideal form, without talking about priorities, and history.
I'm not expecting anything to be perfect. Bugs are always behind the corner. What's scary here is you hiding behind "it's not a bug" and "nobody requested it" and therefore not having the maturity to admit there is a problem.
Also, "properly" makes me wonder how many lines of code have you helped out to shape the library you are using now?
That's not the point here. If the current feature has been designed and implemented in a half-baked way that's not my fault. I'm not part of your team or anything.
What I can say for sure, as a consumer, is that this inconsistent behavior of the library is now delaying a lot our plan and it even blocks the release of the new features in production 'cause it's then breaking the consumption of the API we expose.
With that being said, I provided you all the information you need to reproduce the behavior. If you want to treat this as a bug, a feature or even close it without a solution, it's up to you.
Ok let's go back to basics here: I want to make sure we understand what the specific problem & intent are.
If I understand @cdprete's point -- and apologies for skimming through this -- it is along the lines of wanting to handling of Java 8 Date/Time values to drop precision beyond milliseconds so that one or both of
- When serializing (writing)
Instants, fractional seconds will only include millisecond resolution (whether digits are included for more or not) - When deserializing (reading)
Instants, fractional second parts will only retain millisecond resolution (whether digits are included for more or not)
happens. Is this correct @cdprete ?
The reason I mentioned @JsonFormat is two-fold:
- Specifying pattern would allow solving (1) above (I think), by specifying textual serialization that only includes fraction with 3 digits (milliseconds). It would not, I think, allow solving (2) above tho
- As things are, configuring timestamps (numbers) in Jackson is completely separate from configuring date/time String serializations -- on/off features only affect former (numeric timestamps)
so I thought it might solve the problem -- but writing above, I can see why it might not. I do NOT consider it a "work-around" or "pushing to user to solve" at all. But rather as an existing configuration mechanism.
If so, I think I see the idea wrt 2 features (whether affecting existing ones, or adding new ones) would be to apply pre-/post-processsing to Instant values before serialization into JSON and/or after deserialization from JSON.
Due to backwards-compatibility concerns (which thanks to 3.0.0 GA release now limits both 2.x and 3.x versions), I don't think we can change existing semantics of 2 features. Newly added truncation for users -- as I am fairly certain would happen, break usage somewhere would be nasty surprise.
But we could add 2 new features for truncating values to milliseconds (ideally things should probably be more configurable but that's not what is being asked here), before serialization (on values being serialized), after deserialization (before returning to caller).
Does this sound like a reasonable summary, @cdprete ?
Agreed with @cowtowncoder that existing behavior cannot change.
Since requested behavior is configurable in Jackson2, we might want to target Jackson 3 for our new feature.
Is this correct?
Yes, but please bear in mind it applies to the other types as well.
I never specified I have an issue with Instant only.
I provided the example with it for pure simplicity reasons.
Moreover, to fix with this also https://github.com/FasterXML/jackson-modules-java8/issues/161, the 2 features/flags should be independent from each other.
It should be possible, for example, to serialise date-times up to nanoseconds (for data producers in Java >= 9) but deserialize them up to milliseconds (for data consumers in Java <= 8) or vice versa.
Let's assume that, for example, the 2 new flags are named WRITE_DATE_AS_NANOSECONDS and READ_DATE_AS_NANOSECONDS, then you would have the data producer writing data in nanoseconds (WRITE_DATE_AS_NANOSECONDS is true) and the data consumer reading data in milliseconds (READ_DATA_AS_NANOSECONDS is false).
The opposite could also be possible, if you want to make your data producer backward-compatible with potentially older consumers which can't set these new flags.
And, to conclude the possible scenarios, consumers/producers may decide or not to just stick to milliseconds or not.
I do NOT consider it a "work-around" or "pushing to user to solve" at all. But rather as an existing configuration mechanism.
Fair enough. Generally speaking I agree, but the way it was suggested here it seemed indeed like wanting to push a workaround through consumers. Moreover, assuming that it would be fine, it would be then a technical debt from the moment these ad-hoc code changes would be added to repository, if the only reason for them to be in here is to address what exposed in this ticket.
Due to backwards-compatibility concerns (which thanks to 3.0.0 GA release now limits both 2.x and 3.x versions), I don't think we can change existing semantics of 2 features
Indeed, it was just a suggestion for a potential solution from my side. If I would be a maintainer of the library, I would be highly reluctant to do that as well.
But we could add 2 new features for truncating values to milliseconds (ideally things should probably be more configurable but that's not what is being asked here), before serialization (on values being serialized), after deserialization (before returning to caller).
You can see it as a new feature and therefore do its implementation in 3.x and back port it to 2.x (because you'll have to support 2.x anyway for a certain period of time) or to see it as a bug and do the opposite, therefore implementing it in 2.x and forward port it to 3.x.
Which one is the right way to do it, I can't say. From one side, as a consumer, I would like to have it 2.x because we don't plan to switch immediately to Spring Boot 4 but, from the other side, if I put myself in your shoes, all your efforts are probably focused on 3.x now and therefore it would be simpler for you to find the time to implement it in this version.
Does this sound like a reasonable summary?
@cowtowncoder yes.
Since requested behavior is configurable in Jackson2, we might want to target Jackson 3 for our new feature.
@JooHyukKim I don't even want to reply anymore because, once more, this statement proved to me you either didn't yet understand what the issue is here or you don't want to admit it. @cowtowncoder would it be possible for you to take this over? Thank you.
@cowtowncoder would it be possible for you to take this over? Thank you.
This is a team effort and whoever has time and interest works on things they want to work on.
My interest right now is to make sure the ask is understood so someone can work on this. I don't think I have to work on implementation itself, considering likely effort.
But first things first: I think the next step would be to re-do the request: file a new issue (that refers this issue) that outlines desire for 2 new features (one for truncation-to-millis before serialization; one for truncation-to-millis after deserialization).
An immediate consideration at that point will be 2.x / 3.x aspect: problem being that codebases are now disjoint: 3.x embeds code from date/time module. So it won't be possible to do git merge either way: providing 2.x implementation will be more work (... up to 2x :) ) than 3.x-only.
And to work on all Java 8 time (and date+time) types may be quite a bit of work on its own.
@cdprete I'll file the new request, let you point out what I got wrong.
EDIT: filed FasterXML/jackson-databind#5519
My interest right now is to make sure the ask is understood so someone can work on this. I don't think I have to work on implementation itself, considering likely effort.
Sure. I'm not implying that you've to implement it any way. What I meant is to take over the internal communication within the team so that, from my side, I've to discuss only with one person and not to have to repeat over and over the same things because different people reply to this.
So it won't be possible to do git merge either way: providing 2.x implementation will be more work (... up to 2x :) ) than 3.x-only.
It may be some effort involved, but you can always cherry-pick changes.
And to work on all Java 8 time (and date+time) types may be quite a bit of work on its own.
That's understandable.
What I meant is to take over the internal communication within the team so that, from my side, I've to discuss only with one person and not to have to repeat over and over the same things because different people reply to this.
Yes, I can help facilitate discussions.
It may be some effort involved, but you can always cherry-pick changes.
Only manually tho, not via git. Code has essentially been forked; added in jackson-databind when 3 java8 modules' code was embedded in ObjectMapper.
Only manually tho, not via git. Code has essentially been forked; added in
jackson-databindwhen 3 java8 modules' code was embedded inObjectMapper.
Indeed. I never claimed it would be easy. If the feature gets squashed into only one commit, cherry-picking it plus resolving the conflicts should not be impossible I think.
Only manually tho, not via git. Code has essentially been forked; added in
jackson-databindwhen 3 java8 modules' code was embedded inObjectMapper.Indeed. I never claimed it would be easy. If the feature gets squashed into only one commit, cherry-picking it plus resolving the conflicts should not be impossible I think.
Across repos that have no linkage? I guess git is powerful tool but bit beyond my skill level this one.
Either way, no point debating something that is not happening yet.
Only manually tho, not via git. Code has essentially been forked; added in
jackson-databindwhen 3 java8 modules' code was embedded inObjectMapper.Indeed. I never claimed it would be easy. If the feature gets squashed into only one commit, cherry-picking it plus resolving the conflicts should not be impossible I think.
Across repos that have no linkage? I guess git is powerful tool but bit beyond my skill level this one.
Yes, if locally you track the 2 remotes under different names (e.g.: origin and old). Then you can cherry-pick from one name to another.
@cdprete Implemented this for jackson-databind 3.1(.0), see: https://github.com/FasterXML/jackson-databind/issues/5519 (PR https://github.com/FasterXML/jackson-databind/pull/5520).
@cdprete Implemented this for
jackson-databind3.1(.0), see: FasterXML/jackson-databind#5519 (PR FasterXML/jackson-databind#5520).
Thanks @cowtowncoder. I'll give it a try once I've the chance to update our applications to Spring Boot 4.x.
@cdprete No hurry, 3.1.0 not yet out (altho possible to test with 3.1.0-SNAPSHOT)