Strange behavior with final fields during deserialization

20 views
Skip to first unread message

Clément Poulain

unread,
May 19, 2020, 10:20:55 AM5/19/20
to jackson-user
Hi there,

I'm experience a strange behavior when working with Jackson (v2.11.0) when working with final fields.
I have a POJO with some final fields, which I want to NOT be modifiable during deserialization. It seems to be working as expected for primitive type, but not for objects.

I have created this gist to demonstrate the issue: https://gist.github.com/Plonk42/82409983a684b44df6a564acc43a7d75

The output of this gist is:

1. {"myField":42,"bucket":{"bucketField":42}}
2. myField: 42, bucketField:100
3. {"myField":100,"bucket":{"bucketField":100}}

On line 1., the output is what we expect (both fields set to 42).
Then I'm de-serializing a JSON payload representing a POJO, and we see the first strange thing: "myField" is not modified (still 42) but "bucketField" is modified (now 100).
Then serializing again the resulting POJO, it gets even stranger: now both fields are set to 100.

Am I missing something??

Thanks,
Clément

Clément Poulain

unread,
May 19, 2020, 10:52:10 AM5/19/20
to jackson-user
I have edited the gist to simplify it by using an simple Integer field instead of my own bucket object.

The corresponding output is now:

1. {"myInt":42,"myInteger":42}
2. myInt: 42, myInteger: 100
3. {"myInt":100,"myInteger":100}

Tatu Saloranta

unread,
May 19, 2020, 3:21:26 PM5/19/20
to jackson-user
On Tue, May 19, 2020 at 7:52 AM Clément Poulain <plo...@gmail.com> wrote:
>
> I have edited the gist to simplify it by using an simple Integer field instead of my own bucket object.
>
> The corresponding output is now:
>
> 1. {"myInt":42,"myInteger":42}
> 2. myInt: 42, myInteger: 100
> 3. {"myInt":100,"myInteger":100}

Interesting. I'll run to see what I can see on my end but in the
meantime, I'll mention that there is

MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS

which defaults to enabled; but disabling of which will prevent use of
final fields.
Use of final fields was originally an oversight (no check was done to
see if a visible field is final or not),
and its functioning really depends on how JVM handles it -- my
understanding is that it's sort of a quirk to
allow JDK's own Serialization mechanism (java.io.Serializable)
deserialization otherwise immutable objects,
and that there is some window of opportunity during which such changes
are allowed. And probably there is
also question of visibility of changes, such that in your case changes
are not immediately visible for some
reason.

But I doubt there is much Jackson can do to change what you observe,
which is why disabling above-mentioned
MapperFeature probably makes sense (and only pass values to final
fields via `@JsonCreator`)

-+ Tatu +-

>
> On Tuesday, May 19, 2020 at 4:20:55 PM UTC+2, Clément Poulain wrote:
>>
>> Hi there,
>>
>> I'm experience a strange behavior when working with Jackson (v2.11.0) when working with final fields.
>> I have a POJO with some final fields, which I want to NOT be modifiable during deserialization. It seems to be working as expected for primitive type, but not for objects.
>>
>> I have created this gist to demonstrate the issue: https://gist.github.com/Plonk42/82409983a684b44df6a564acc43a7d75
>>
>> The output of this gist is:
>>
>> 1. {"myField":42,"bucket":{"bucketField":42}}
>> 2. myField: 42, bucketField:100
>> 3. {"myField":100,"bucket":{"bucketField":100}}
>>
>> On line 1., the output is what we expect (both fields set to 42).
>> Then I'm de-serializing a JSON payload representing a POJO, and we see the first strange thing: "myField" is not modified (still 42) but "bucketField" is modified (now 100).
>> Then serializing again the resulting POJO, it gets even stranger: now both fields are set to 100.
>>
>> Am I missing something??
>>
>> Thanks,
>> Clément
>
> --
> You received this message because you are subscribed to the Google Groups "jackson-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/e183668f-4309-42a9-ad06-ca54848a40e2%40googlegroups.com.

Tatu Saloranta

unread,
May 19, 2020, 3:28:59 PM5/19/20
to jackson-user
On Tue, May 19, 2020 at 12:21 PM Tatu Saloranta <ta...@fasterxml.com> wrote:
>
> On Tue, May 19, 2020 at 7:52 AM Clément Poulain <plo...@gmail.com> wrote:
> >
> > I have edited the gist to simplify it by using an simple Integer field instead of my own bucket object.
> >
> > The corresponding output is now:
> >
> > 1. {"myInt":42,"myInteger":42}
> > 2. myInt: 42, myInteger: 100
> > 3. {"myInt":100,"myInteger":100}
>
> Interesting. I'll run to see what I can see on my end but in the
> meantime, I'll mention that there is

I see the same results as you, and they seem to be stable too (and not
due to just temporary invisibility of changes).
Perhaps this is due to some optimization of direct access, wherein
what is declared as a constant is assumed to be
so (although I thought that would only be done for `static final`
members?); whereas access through Reflection
will find actual modified value.

-+ Tatu +-

Clément Poulain

unread,
May 19, 2020, 3:42:37 PM5/19/20
to jackson-user
I think the answer can be found in this post, in "Remember Constant Expressions" section:  https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection/3301720#3301720


" If a final field is initialized to a compile-time constant expression (§15.28) in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the value of the constant expression."
> > To unsubscribe from this group and stop receiving emails from it, send an email to jackso...@googlegroups.com.

Clément Poulain

unread,
May 19, 2020, 3:45:30 PM5/19/20
to jackson-user
> MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS

Yes, I played with that also but encountered another issue. I'll open a dedicated thread!
> To unsubscribe from this group and stop receiving emails from it, send an email to jackso...@googlegroups.com.

Tatu Saloranta

unread,
May 19, 2020, 5:00:13 PM5/19/20
to jackson-user
On Tue, May 19, 2020 at 12:42 PM Clément Poulain <plo...@gmail.com> wrote:
>
> I think the answer can be found in this post, in "Remember Constant Expressions" section: https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection/3301720#3301720
>
> It links to: https://stackoverflow.com/questions/17506329/java-final-field-compile-time-constant-expression
> and: https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
>
> " If a final field is initialized to a compile-time constant expression (§15.28) in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the value of the constant expression."

That makes sense wrt symptoms here. Thank you for the link, can refer
others to that.

In this particular case, then, it's the actual initializer that causes
it then, I think. Too bad there is no straight-forward way to figure
out
this case via Reflection, to filter out such "true" constant cases (vs
cases where initalization is from constructor).
But at least it is good to know what triggers this.
> To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/056deaa2-8837-4c3c-a751-9725c8b50fc4%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages