Repository navigation
Constructor binding: absent vs null #6063
Copy link
Copy link
Open
Description
I have a strict json mapper, forbidding all type coercions:
JsonMapper.builder()
.withCoercionConfigDefaults(coercionConfig -> {
coercionConfig.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail);
})
.changeDefaultNullHandling(_ -> JsonSetter.Value.construct(Nulls.FAIL, Nulls.FAIL))
.build()
And I would like to use my DTOs with constructors instead of setters:
public class TestClass {
private String propertyA;
private String propertyB;
// This.
public TestClass(String propertyA, String propertyB) {
this.propertyA = propertyA;
this.propertyB = propertyB;
}
// Over this.
// public void setPropertyA(String propertyA) {
// this.propertyA = propertyA;
// }
public String getPropertyA() {
return propertyA;
}
// public void setPropertyB(String propertyB) {
// this.propertyB = propertyB;
// }
public String getPropertyB() {
return propertyB;
}
}
If I do this:
jsonMapper.readValue("{ \"propertyA\": \"value\" }", TestClass.class);
I will obtain the following exception:
Invalid `null` value encountered for property "propertyB"
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: TestClass["propertyB"])
tools.jackson.databind.exc.InvalidNullException: Invalid `null` value encountered for property "propertyB"
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: TestClass["propertyB"])
I would expect this error to be thrown if I provided \"propertyB\": null but not in this situation.
I understand that this is caused by my null configuration, however, we need to keep it.
Is what I am trying to achieve possible out of the box? That is, not providing propertyB is acceptable while providing it with value null is not (absent is ok, explicit null is forbidden).
Originally reported in #3847 in a slightly different manner.
Versions used:
- com.fasterxml.jackson.core:jackson-annotations:2.21
- tools.jackson.core:jackson-core:3.1.4
- tools.jackson.core:jackson-databind:3.1.4
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
pjfanning commentedon Jun 30, 2026
@joca-bt does opening a new issue help? I think not. It splits the history. Closing this.
joca-bt commentedon Jun 30, 2026
@pjfanning the other ticket was closed up without a solution and afaik you don't reopen issues.
This is also for version 3 not 2.
pjfanning commentedon Jun 30, 2026
@joca-bt Fair enough. Reopened. I'm using my mobile and didn't notice the old issue was closed.
cowtowncoder commentedon Jul 2, 2026
Quick question/confirmation: does 3.2.0 behave the same?
Other than that, this may be tricky one as absent value must somehow be mapped into constructor parameter -- it cannot be just omitted like setter call or field assignment can -- and for that,
nullcoercion rules are to be applied.But you should be able to change handling of nulls for specific parameter with
@JsonSetterI think; to override global settings.Maybe that is the work-around to use?
joca-bt commentedon Jul 2, 2026
3.2.0 seems to behave the same way.
I was able to find a workaround.
I added a custom
NullValueProviderwhich returns null forgetAbsentValueinstead of callinggetNullValue(which throws an error forNulls.FAIL). I registered it through aValueDeserializerModifier.However, I wonder if this should be the default behaviour for
NullsFailProviderand others.When using setters instead of constructor,
getAbsentValueis skipped, and the property will get the default Java value (null for objects, something else for primitives). From this perspective, the behaviour is different between both usages (setters vs constructor). Should the behaviour be the same?My expectation for null coercion rules was that they would be applied only when null is actually provided by the client, which is not this case.
I arrived at my workaround with some help from Claude, here is the session if you are interested, it has some interesting analysis.
session-history.md
cowtowncoder commentedon Jul 2, 2026
I think that many/most users want missing and explicit
nullboth fail so changing default behavior is probably not possible or desired.But I would be open to new
MapperFeatureto allow opt-in change?On constructors vs setters, behavior difference... this is not the only difference so I think that while unifying behavior is a good goal, in practice documenting things is necessary.
F.ex,
requiredis only ever enforced for Constructor-passed properties.joca-bt commentedon Jul 6, 2026
An in-built feature would be great!
I went with the workaround below (still needs to pass proper testing):
joca-bt commentedon Jul 21, 2026
@cowtowncoder do you have an idea of the name for the feature? I could try working on an implementation if so.
cowtowncoder commentedon Jul 21, 2026
@joca-bt No good name comes to mind immediately. You can use a placeholder for now...
ABSENT_BEHAVES_LIKE_NULL?Also, depending on implementation,
DeserializationFeaturemight work better thanMapperFeature(since then it can be dynamically changed forObjectReaders).joca-bt commentedon Aug 10, 2026
Sorry, I won't have time to pick this up anytime soon. What I was thinking was adding a new null provider and use it when the feature is on.