Background

We added this to the OrganizationAdminMapper for OrganizationCreateAdminRequest:

@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
Organization toEntity(OrganizationCreateAdminRequest organizationDTO);

Expected behavior: If organizationStatus or organizationTier is null in the request, the default values defined in the entity (ACTIVE, NOT_DETERMINED) would remain untouched.

Reality: null values overrode the defaults.


Debug Task

package com.sensey.fepatex.common.debug.task.organization;

import com.sensey.fepatex.common.debug.task.DebugTask;
import com.sensey.fepatex.domain.organization.dto.private_api.admin_api.request.OrganizationCreateAdminRequest;
import com.sensey.fepatex.domain.organization.entity.Organization;
import com.sensey.fepatex.domain.organization.entity.embedded.OrganizationContact;
import com.sensey.fepatex.domain.organization.entity.embedded.OrganizationFinance;
import com.sensey.fepatex.domain.organization.enums.OrganizationStatus;
import com.sensey.fepatex.domain.organization.enums.OrganizationTier;
import com.sensey.fepatex.domain.organization.enums.OrganizationType;
import com.sensey.fepatex.domain.organization.mapper.private_api.admin_api.OrganizationAdminMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.time.LocalDate;

@Component
@Profile("local")
@RequiredArgsConstructor
@Slf4j
public class OrganizationMapperDebugTask implements DebugTask {

    private final OrganizationAdminMapper organizationAdminMapper;

    @Override
    public void execute() {
        log.info(" Start OrganizationMapperDebugTask");

        OrganizationCreateAdminRequest request = new OrganizationCreateAdminRequest(
            "Acme Corp",
            "12345678",
            "<https://cdn.example.com/logo.png>",
            "Manufacturing",
            "Plastics",
            LocalDate.of(2022, 1, 1),
            "Long-term strategic partner.",
            null,
            null,
            null,
            null,
            OrganizationType.CUSTOMER,
            null,
            new OrganizationContact(),
            new OrganizationFinance()
        );

        Organization org = organizationAdminMapper.toEntity(request);

        log.info(">>> Mapped Organization Entity");
        log.info("Name: {}", org.getName());
        log.info("KvK: {}", org.getCocNumber());
        log.info("Status: {}", org.getOrganizationStatus());
        log.info("Tier: {}", org.getOrganizationTier());
        log.info("Type: {}", org.getOrganizationType());

        if (org.getOrganizationStatus() == OrganizationStatus.ACTIVE) {
            log.info("Default OrganizationStatus 'ACTIVE' applied");
        } else {
            log.warn("OrganizationStatus was not set to default value ACTIVE");
        }

        if (org.getOrganizationTier() == OrganizationTier.NOT_DETERMINED) {
            log.info("Default OrganizationTier 'NOT_DETERMINED' applied");
        }
        else {
            log.warn("OrganizationTier was not set to default value NOT_DETERMINED");
        }

        log.info(" End OrganizationMapperDebugTask");
    }
}


Debug Output

>>> Mapped Organization Entity
Name: Acme Corp
KvK: 12345678
Status: null
Tier: null
Type: CUSTOMER
OrganizationStatus was not set to default value ACTIVE
OrganizationTier was not set to default value NOT_DETERMINED

Entity Snippet

@Builder.Default
@Enumerated(EnumType.STRING)
@JdbcType(PostgreSQLEnumJdbcType.class)
@Column(name = "organization_status", nullable = false)
private OrganizationStatus organizationStatus = OrganizationStatus.ACTIVE;

@Builder.Default
@Enumerated(EnumType.STRING)
@JdbcType(PostgreSQLEnumJdbcType.class)
@Column(name = "organization_tier", nullable = false)
private OrganizationTier organizationTier = OrganizationTier.NOT_DETERMINED;


DTO Snippet

public record OrganizationCreateAdminRequest(
    String name,
    String cocNumber,
    String photoURL,
    String primarySector,
    String secondarySector,
    LocalDate startDate,
    String notes,

    OrganizationStatus organizationStatus,
    OrganizationSource organizationSource,
    OrganizationTier organizationTier,
    OrganizationDivision organizationDivision,
    OrganizationType organizationType,

    UUID accountManagerId,
    OrganizationContact contact,
    OrganizationFinance finance
) {}


Root Cause

Even with @BeanMapping(nullValuePropertyMappingStrategy = IGNORE), MapStruct still maps null explicitly provided in the source DTO, effectively overriding the defaults in the target entity.