Java records combined with explicit builder patterns provide immutable data classes that work seamlessly with MapStruct's builder detection. This approach leverages records' automatic getters and compact constructors while adding the required builder infrastructure for mapping.
Complete Record Target Class
The Person record with builder support:
public record Person(
Long id,
String firstName,
String lastName
) {
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Long id;
private String firstName;
private String lastName;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public Builder lastName(String lastName) {
this.lastName = lastName;
return this;
}
public Person build() {
return new Person(id, firstName, lastName);
}
}
}
Complete Source Class
Simple mutable source class for demonstration:
public class ExternalPerson {
private Long id;
private String personFirstName;
private String personLastName;
public ExternalPerson(Long id, String personFirstName, String personLastName) {
this.id = id;
this.personFirstName = personFirstName;
this.personLastName = personLastName;
}
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPersonFirstName() {
return personFirstName;
}
public void setPersonFirstName(String personFirstName) {
this.personFirstName = personFirstName;
}
public String getPersonLastName() {
return personLastName;
}
public void setPersonLastName(String personLastName) {
this.personLastName = personLastName;
}
}
Complete Mapper Interface
MapStruct automatically detects the record's static builder() method:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper
public interface PersonMapper {
@Mapping(source = "personFirstName", target = "firstName")
@Mapping(source = "personLastName", target = "lastName")
Person toPerson(ExternalPerson source);
}
Generated Mapping Code
MapStruct generates efficient builder-based mapping:
import javax.annotation.processing.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
comments = "version: 1.6.3, compiler: IncrementalProcessingEnvironment from gradle-language-java-9.2.0.jar, environment: Java 25 (Oracle Corporation)"
)
public class PersonMapperImpl implements PersonMapper {
@Override
public Person toPerson(ExternalPerson source) {
if ( source == null ) {
return null;
}
Person.Builder person = Person.builder();
person.firstName( source.getPersonFirstName() );
person.lastName( source.getPersonLastName() );
person.id( source.getId() );
return person.build();
}
}
Custom Builder Method Names
Override defaults using @Builder annotation on the mapper:
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(builder = @Builder(buildMethod = "construct"))
public abstract class PersonMapper {
@Mapping(source = "personFirstName", target = "firstName")
@Mapping(source = "personLastName", target = "lastName")
abstract Person toPerson(ExternalPerson source);
}
For this configuration, change the record to:
// In Builder class:
public Person construct() { // Changed from build()
return new Person(id, firstName, lastName);
}
Key Benefits of Record + Builder Pattern
- Records provide automatic
equals(),hashCode(),toString(), and getters - Immutable by default with final components
- Builder enables fluent construction for complex initialization
- MapStruct auto-detects standard
builder()+build()pattern
Leave a Reply