Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
<!-- region Dependency Versions -->
<!-- Validator 8+ requires Java 11, Validator 9+ requires Java 17. -->
<hibernate-validator.version>7.0.5.Final</hibernate-validator.version>
<jackson-bom.version>2.21.0</jackson-bom.version>
<jackson2-bom.version>2.21.0</jackson2-bom.version>
<jackson3-bom.version>3.0.4</jackson3-bom.version>
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Jackson 3 requires Java 17+, but the build is pinned to Java 8. Adding the Jackson 3 BOM/tools.jackson dependencies will make builds fail with class file version incompatibility unless the toolchain is upgraded or Jackson 3 is isolated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pom.xml, line 40:

<comment>Jackson 3 requires Java 17+, but the build is pinned to Java 8. Adding the Jackson 3 BOM/tools.jackson dependencies will make builds fail with class file version incompatibility unless the toolchain is upgraded or Jackson 3 is isolated.</comment>

<file context>
@@ -36,7 +36,8 @@
         <hibernate-validator.version>7.0.5.Final</hibernate-validator.version>
-        <jackson-bom.version>2.20.1</jackson-bom.version>
+        <jackson2-bom.version>2.20.1</jackson2-bom.version>
+        <jackson3-bom.version>3.0.4</jackson3-bom.version>
         <javax-validation-api.version>2.0.1.Final</javax-validation-api.version>
         <jakarta-validation-api.version>3.1.1</jakarta-validation-api.version>
</file context>
Fix with Cubic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fair point, however, locally I was able to build (and use) this without any issue. (Obviously I have JDK21 installed, but the maven build had no issues with java.version set to 8)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the .github/workflows should be updated then to JDK17

<javax-validation-api.version>2.0.1.Final</javax-validation-api.version>
<jakarta-validation-api.version>3.1.1</jakarta-validation-api.version>
<junit.version>5.14.2</junit.version>
Expand All @@ -60,10 +61,17 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson-bom.version}</version>
<version>${jackson2-bom.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>tools.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson3-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
Expand All @@ -78,6 +86,12 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.util.NameTransformer;

public class JsonNullableBeanPropertyWriter extends BeanPropertyWriter
public class JsonNullableJackson2BeanPropertyWriter extends BeanPropertyWriter
{
private static final long serialVersionUID = 1L;

protected JsonNullableBeanPropertyWriter(BeanPropertyWriter base) {
protected JsonNullableJackson2BeanPropertyWriter(BeanPropertyWriter base) {
super(base);
}

protected JsonNullableBeanPropertyWriter(BeanPropertyWriter base, PropertyName newName) {
protected JsonNullableJackson2BeanPropertyWriter(BeanPropertyWriter base, PropertyName newName) {
super(base, newName);
}

@Override
protected BeanPropertyWriter _new(PropertyName newName) {
return new JsonNullableBeanPropertyWriter(this, newName);
return new JsonNullableJackson2BeanPropertyWriter(this, newName);
}

@Override
public BeanPropertyWriter unwrappingWriter(NameTransformer unwrapper) {
return new UnwrappingJsonNullableBeanPropertyWriter(this, unwrapper);
return new UnwrappingJsonNullableJackson2BeanPropertyWriter(this, unwrapper);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import java.util.List;

public class JsonNullableBeanSerializerModifier extends BeanSerializerModifier
public class JsonNullableJackson2BeanSerializerModifier extends BeanSerializerModifier
{
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
Expand All @@ -19,7 +19,7 @@ public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
final BeanPropertyWriter writer = beanProperties.get(i);
JavaType type = writer.getType();
if (type.isTypeOrSubTypeOf(JsonNullable.class)) {
beanProperties.set(i, new JsonNullableBeanPropertyWriter(writer));
beanProperties.set(i, new JsonNullableJackson2BeanPropertyWriter(writer));
}
}
return beanProperties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import java.io.IOException;

public class JsonNullableDeserializer extends ReferenceTypeDeserializer<JsonNullable<Object>> {
public class JsonNullableJackson2Deserializer extends ReferenceTypeDeserializer<JsonNullable<Object>> {

private static final long serialVersionUID = 1L;

Expand All @@ -25,8 +25,8 @@ public class JsonNullableDeserializer extends ReferenceTypeDeserializer<JsonNull
/* Life-cycle
/**********************************************************
*/
public JsonNullableDeserializer(JavaType fullType, ValueInstantiator inst,
TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
public JsonNullableJackson2Deserializer(JavaType fullType, ValueInstantiator inst,
TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
super(fullType, inst, typeDeser, deser);
if (fullType instanceof ReferenceType && ((ReferenceType) fullType).getReferencedType() != null) {
this.isStringDeserializer = ((ReferenceType) fullType).getReferencedType().isTypeOrSubTypeOf(String.class);
Expand All @@ -52,8 +52,8 @@ public JsonNullable<Object> deserialize(JsonParser p, DeserializationContext ctx
}

@Override
public JsonNullableDeserializer withResolved(TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
return new JsonNullableDeserializer(_fullType, _valueInstantiator,
public JsonNullableJackson2Deserializer withResolved(TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
return new JsonNullableJackson2Deserializer(_fullType, _valueInstantiator,
typeDeser, valueDeser);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.ReferenceType;

public class JsonNullableDeserializers extends Deserializers.Base {
public class JsonNullableJackson2Deserializers extends Deserializers.Base {

@Override
public JsonDeserializer<?> findReferenceDeserializer(ReferenceType refType,
DeserializationConfig config, BeanDescription beanDesc,
TypeDeserializer contentTypeDeserializer, JsonDeserializer<?> contentDeserializer) {
return (refType.hasRawClass(JsonNullable.class)) ? new JsonNullableDeserializer(refType, null, contentTypeDeserializer,contentDeserializer) : null;
return (refType.hasRawClass(JsonNullable.class)) ? new JsonNullableJackson2Deserializer(refType, null, contentTypeDeserializer,contentDeserializer) : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.fasterxml.jackson.databind.type.ReferenceType;
import com.fasterxml.jackson.databind.util.NameTransformer;

public class JsonNullableSerializer extends ReferenceTypeSerializer<JsonNullable<?>> {
public class JsonNullableJackson2Serializer extends ReferenceTypeSerializer<JsonNullable<?>> {

private static final long serialVersionUID = 1L;

Expand All @@ -17,14 +17,14 @@ public class JsonNullableSerializer extends ReferenceTypeSerializer<JsonNullable
/**********************************************************
*/

protected JsonNullableSerializer(ReferenceType fullType, boolean staticTyping,
TypeSerializer vts, JsonSerializer<Object> ser) {
protected JsonNullableJackson2Serializer(ReferenceType fullType, boolean staticTyping,
TypeSerializer vts, JsonSerializer<Object> ser) {
super(fullType, staticTyping, vts, ser);
}

protected JsonNullableSerializer(JsonNullableSerializer base, BeanProperty property,
TypeSerializer vts, JsonSerializer<?> valueSer, NameTransformer unwrapper,
Object suppressableValue)
protected JsonNullableJackson2Serializer(JsonNullableJackson2Serializer base, BeanProperty property,
TypeSerializer vts, JsonSerializer<?> valueSer, NameTransformer unwrapper,
Object suppressableValue)
{
// Keep suppressNulls to false to always serialize JsonNullable[null]
super(base, property, vts, valueSer, unwrapper,
Expand All @@ -36,15 +36,15 @@ protected ReferenceTypeSerializer<JsonNullable<?>> withResolved(BeanProperty pro
TypeSerializer vts, JsonSerializer<?> valueSer,
NameTransformer unwrapper)
{
return new JsonNullableSerializer(this, prop, vts, valueSer, unwrapper,
return new JsonNullableJackson2Serializer(this, prop, vts, valueSer, unwrapper,
_suppressableValue);
}

@Override
public ReferenceTypeSerializer<JsonNullable<?>> withContentInclusion(Object suppressableValue,
boolean suppressNulls)
{
return new JsonNullableSerializer(this, _property, _valueTypeSerializer,
return new JsonNullableJackson2Serializer(this, _property, _valueTypeSerializer,
_valueSerializer, _unwrapper,
suppressableValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.fasterxml.jackson.databind.ser.Serializers;
import com.fasterxml.jackson.databind.type.ReferenceType;

public class JsonNullableSerializers extends Serializers.Base {
public class JsonNullableJackson2Serializers extends Serializers.Base {

@Override
public JsonSerializer<?> findReferenceSerializer(SerializationConfig config,
Expand All @@ -17,7 +17,7 @@ public JsonSerializer<?> findReferenceSerializer(SerializationConfig config,
if (JsonNullable.class.isAssignableFrom(refType.getRawClass())) {
boolean staticTyping = (contentTypeSerializer == null)
&& config.isEnabled(MapperFeature.USE_STATIC_TYPING);
return new JsonNullableSerializer(refType, staticTyping,
return new JsonNullableJackson2Serializer(refType, staticTyping,
contentTypeSerializer, contentValueSerializer);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import java.lang.reflect.Type;

public class JsonNullableTypeModifier extends TypeModifier {
public class JsonNullableJackson2TypeModifier extends TypeModifier {

@Override
public JavaType modifyType(JavaType type, Type jdkType, TypeBindings bindings, TypeFactory typeFactory)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.openapitools.jackson.nullable;

import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.PropertyName;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ser.BeanPropertyWriter;
import tools.jackson.databind.util.NameTransformer;

public class JsonNullableJackson3BeanPropertyWriter extends BeanPropertyWriter {

protected JsonNullableJackson3BeanPropertyWriter(BeanPropertyWriter base) {
super(base);
}

protected JsonNullableJackson3BeanPropertyWriter(BeanPropertyWriter base, PropertyName newName) {
super(base, newName);
}

@Override
protected BeanPropertyWriter _new(PropertyName newName) {
return new JsonNullableJackson3BeanPropertyWriter(this, newName);
}

@Override
public BeanPropertyWriter unwrappingWriter(NameTransformer unwrapper) {
return new UnwrappingJsonNullableJackson3BeanPropertyWriter(this, unwrapper);
}

@Override
public void serializeAsProperty(Object bean, JsonGenerator jgen, SerializationContext ctxt) throws Exception {
Object value = get(bean);
if (JsonNullable.undefined().equals(value) || (_nullSerializer == null && value == null)) {
return;
}
super.serializeAsProperty(bean, jgen, ctxt);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.openapitools.jackson.nullable;


import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.DeserializationConfig;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.deser.ValueInstantiator;
import tools.jackson.databind.deser.std.ReferenceTypeDeserializer;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.type.ReferenceType;

public class JsonNullableJackson3Deserializer extends ReferenceTypeDeserializer<JsonNullable<Object>> {


private boolean isStringDeserializer = false;

/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public JsonNullableJackson3Deserializer(JavaType fullType, ValueInstantiator inst,
TypeDeserializer typeDeser, ValueDeserializer<?> deser) {
super(fullType, inst, typeDeser, deser);
if (fullType instanceof ReferenceType && ((ReferenceType) fullType).getReferencedType() != null) {
this.isStringDeserializer = ((ReferenceType) fullType).getReferencedType().isTypeOrSubTypeOf(String.class);
}
}

/*
/**********************************************************
/* Abstract method implementations
/**********************************************************
*/

@Override
public JsonNullable<Object> deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
JsonToken t = p.currentToken();
if (t == JsonToken.VALUE_STRING && !isStringDeserializer) {
String str = p.getString().trim();
if (str.isEmpty()) {
return JsonNullable.undefined();
}
}
return super.deserialize(p, ctxt);
}

@Override
protected ReferenceTypeDeserializer<JsonNullable<Object>> withResolved(TypeDeserializer typeDeser, ValueDeserializer<?> valueDeser) {
return new JsonNullableJackson3Deserializer(_fullType, _valueInstantiator,
typeDeser, valueDeser);
}

@Override
public Object getAbsentValue(DeserializationContext ctxt) {
return JsonNullable.undefined();
}

@Override
public JsonNullable<Object> getNullValue(DeserializationContext ctxt) {
return JsonNullable.of(null);
}

@Override
public Object getEmptyValue(DeserializationContext ctxt) {
return JsonNullable.undefined();
}

@Override
public JsonNullable<Object> referenceValue(Object contents) {
return JsonNullable.of(contents);
}

@Override
public Object getReferenced(JsonNullable<Object> reference) {
return reference.get();
}

@Override
public JsonNullable<Object> updateReference(JsonNullable<Object> reference, Object contents) {
return JsonNullable.of(contents);
}

@Override
public Boolean supportsUpdate(DeserializationConfig config) {
// yes; regardless of value deserializer reference itself may be updated
return Boolean.TRUE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.openapitools.jackson.nullable;

import tools.jackson.databind.DeserializationConfig;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.BeanDescription.Supplier;
import tools.jackson.databind.deser.Deserializers;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.type.ReferenceType;

public class JsonNullableJackson3Deserializers extends Deserializers.Base {

@Override
public ValueDeserializer<?> findReferenceDeserializer(ReferenceType refType,
DeserializationConfig config, Supplier beanDescRef,
TypeDeserializer contentTypeDeserializer, ValueDeserializer<?> contentDeserializer) {
return (refType.hasRawClass(JsonNullable.class)) ? new JsonNullableJackson3Deserializer(refType, null, contentTypeDeserializer,contentDeserializer) : null;
}

@Override
public boolean hasDeserializerFor(DeserializationConfig config, Class<?> valueType) {
return JsonNullable.class.equals(valueType);
}
}
Loading