Skip to content

Evaluate @Nullable annotations to support combining mapstruct with uber/nullaway #3182

@Richargh

Description

@Richargh

Use case

The code generated by MapStruct is not null-aware and this creates problems when using the annotation processer nullaway. Nullaway assumes all code to be non-null by default and all nullable values to be annotated with @Nullable. Unfortunately MapStruct does not generate compatible code which means we have to turn off nullaway for the generated code which defeats the purpose a bit.

Problem

This does not compile:

public record RenterDto(String id, String name) {
}

public record Renter(RenterId id, String name) {
}

public interface RenterTranslator {

    @Mapping(source = "id", target = "id.rawValue")
    @Nullable Renter dtoToRenter(@Nullable RenterDto dto);

    @Mapping(source = "id.rawValue", target = "id")
    @Nullable RenterDto RenterToDto(@Nullable Renter renter);
}

because it generated this code:

@Override
    public Renter dtoToRenter(RenterDto dto) {
        if ( dto == null ) {
            return null; // <-- returns null which is not ok because the return value is not marked as @Nullable
        }

        RenterId id = null; // <!-- not the correct default value, null is not allowed, but nullaway is smart enough the figure out that id will be set to a correct value below
        String name = null;

        id = renterDtoToRenterId( dto );
        name = dto.name();

        Renter renter = new Renter( id, name );

        return renter;
    }

If we now add things that are actually nullable, like who rented an item, things become more complex:

public record ItemDto(String id, String name, @Nullable String rentedById) {

}

public record Item(ItemId id, String name, @Nullable RenterId rentedBy) {
}

Now the MapStruct code needs to know that it should only unwrap rentedBy if it is non-null and only wrap rentedById when it is non-null. Otherwise null-away will throw an error again.

Complete example

The complete example can be found here: https://github.com/Richargh/mapstruct-nullaway-mvn-java-limitation

Wish

I'd like MapStruct to interpret @nullable annotations as such. That would also increase the null-safety when using MapStruct with Kotlin.

Generated Code

@Override
    public Renter dtoToRenter(RenterDto dto) {
        RenterId id = renterDtoToRenterId( dto );
        String name = dto.name();

        Renter renter = new Renter( id, name );

        return renter;
    }

protected RenterId renterDtoToRenterId(RenterDto renterDto) {
        String rawValue = renterDto.id();

        RenterId renterId = new RenterId( rawValue );

        return renterId;
    }

and

@Override
    public Item dtoToItem(ItemDto dto) {
        ItemId id = itemDtoToItemId( dto );
        String name = dto.name();

        RenterId rentedBy = dto.rentedById() == null ? null : new RenterId(dto.rentedById());

        Item item = new Item( id, name, rentedBy );

        return item;
    }

Possible workarounds

Let nullaway ignore @generated code, which unfortunately defeats the purpose of null-safety.

MapStruct Version

1.5.3.Final

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions