Skip to content

DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES treats absent field same as explicit null #5734

@MartinUhlen

Description

@MartinUhlen

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

While upgrading to Jackson 3 / Spring Boot 4 I noticed that DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES is now true by default, it defaulted to false in Jackson 2.

I agree that it's a good default to fail on attempt to deserialize JSON with null to a primitive, as primitives cannot be null.

But why does it fail on absent values? If I leave out a property for a reference field (String, LocalDate etc.) Jackson will default this field to null as expected, so I'd expect it would default boolean to false , int to 0 etc. Isn't this an unnecessary discrepancy between Java reference types and Java primitive types? Why is absent JSON fields for Java reference types defaulted but primitive fails?

Version Information

3.0.4

Reproduction

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;

import org.junit.jupiter.api.Test;

import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;

class JacksonDeserializationTest {

    private static final JsonMapper JSON_MAPPER = JsonMapper.builder()
            .build();

    record StringRecord(String foo, String bar) {
    }

    record PrimitiveRecord(int int1, int int2, boolean boolean1, boolean boolean2) {
    }

    @Test
    void shouldDeserializeAbsentStrings() {
        // Given
        String json = """
        {
            "bar": "barValue"
        }
        """;

        // When
        StringRecord actual = JSON_MAPPER.readValue(json, StringRecord.class);

        // Then
        assertThat(actual)
                .as("Expect absent 'foo' to have default values (null for references)")
                .isEqualTo(new StringRecord(null, "barValue"));
    }

    @Test
    void shouldDeserializeAbsentPrimitives() {
        // Given
        String json = """
        {
            "int2": 42,
            "boolean1": true
        }
        """;

        // When
        PrimitiveRecord actual = JSON_MAPPER.readValue(json, PrimitiveRecord.class);

        // Then
        assertThat(actual)
                .as("Expect absent int1 and boolean2 have default values (0 and false)")
                .isEqualTo(new PrimitiveRecord(0, 42, true, false));
    }

    @Test
    void shouldFailDeserializeNullPrimitives() {
        // Given
        String json = """
        {
            "int1": 111
            "int2": 222,
            "boolean1": true,
            "boolean2": null
        }
        """;

        // When
        Throwable thrown = catchThrowable(() -> JSON_MAPPER.readValue(json, PrimitiveRecord.class));

        // Then
        assertThat(thrown)
                .as("Expect mapping null to primitive fails")
                .isInstanceOf(JacksonException.class);
    }
}

Expected behavior

I'd expect shouldDeserializeAbsentPrimitives() to pass with Jackson 3 using default configuration as it does with Jackson 2 with default configuration.

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions