How to set up Spring Boot with
MapStruct and Lombok (2026)
MapStruct and Lombok are two of the most useful libraries in a Spring Boot project. Configuring both in Maven is surprisingly easy to get wrong — the annotation processor order matters, and there’s a bridge dependency most tutorials miss.
MapStruct and Lombok are two of the most useful libraries in a Spring Boot project. Lombok eliminates boilerplate in your entity and DTO classes. MapStruct generates the mapping code between them at compile time — no reflection, no runtime overhead.
The problem: configuring both in the same Maven project is surprisingly easy to get wrong. The annotation processors must be declared in the right order, and the Lombok-MapStruct integration requires a specific bridge dependency. Get it wrong and your mappers silently ignore Lombok-generated constructors.
Why use both Lombok and MapStruct?
In a standard Spring Boot layered architecture, you maintain two representations of each domain object: a JPA entity and a DTO. Without tooling this means writing getters, setters, constructors, equals, and hashCode in every class, and a conversion method that copies fields by hand. Together, Lombok and MapStruct eliminate both.
To understand why the DTO layer exists, read the article on Spring Boot project structure best practices.
The pom.xml configuration
This is the part most tutorials get wrong. Lombok must run before MapStruct. If MapStruct runs first, it cannot see the constructors and builders that Lombok generates, and your mappers fall back to field access or fail silently.
Step 1 — Dependency versions in <properties>
<properties>
<java.version>17</java.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok.version>1.18.32</lombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Step 2 — Add the dependencies
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
Step 3 — Configure the maven-compiler-plugin
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<!-- 1. Lombok MUST come first -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<!-- 2. Bridge between Lombok and MapStruct -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
<!-- 3. MapStruct processor last -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
The lombok-mapstruct-binding artifact is the bridge that ensures the two processors cooperate. Without it, behavior depends on the JDK version — it works sometimes and breaks silently in others. Always include it.
Entity and DTO with Lombok
Entity
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "products")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@Column(nullable = false)
private Double price;
}
Use @Getter and @Setter instead of @Data on JPA entities. @Data generates equals and hashCode based on all fields — which causes problems with bidirectional relationships and Hibernate’s identity mechanism.
DTO
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProductDto {
private Long id;
private String name;
private String description;
private Double price;
}
On DTOs, @Data is fine — there are no JPA relationships involved.
Writing the MapStruct mapper
import org.mapstruct.*;
@Mapper(componentModel = "spring")
public interface ProductMapper {
ProductDto toDto(Product entity);
Product toEntity(ProductDto dto);
List<ProductDto> toDtoList(List<Product> entities);
}
componentModel = "spring" tells MapStruct to generate a Spring bean. The implementation is generated at compile time into target/generated-sources/annotations — plain, readable Java.
If field names differ between entity and DTO, use @Mapping:
@Mapping(source = "entity.productName", target = "name")
ProductDto toDto(Product entity);
Using the mapper in the service layer
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {
private final ProductRepository productRepository;
private final ProductMapper productMapper;
@Override
public ProductDto findById(Long id) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Product not found: " + id));
return productMapper.toDto(product);
}
@Override
@Transactional
public ProductDto create(ProductDto dto) {
Product product = productMapper.toEntity(dto);
return productMapper.toDto(productRepository.save(product));
}
}
@RequiredArgsConstructor generates a constructor for all final fields — constructor injection with zero boilerplate. This is the exact pattern used in every project generated by SpringBoot Generator.
Common issues and fixes
# MapStruct generates empty mapper — all methods return null
Cause: MapStruct ran before Lombok and could not see the constructors.
Fix: Check the annotationProcessorPaths order. Lombok must come first. Add lombok-mapstruct-binding.
# cannot find symbol on Lombok-generated methods
Cause: Lombok is in <dependencies> but not in <annotationProcessorPaths>.
Fix: Add Lombok to both sections as shown above.
# IntelliJ IDEA does not see generated sources
Fix: Right-click target/generated-sources/annotations → Mark Directory as → Generated Sources Root. Or enable annotation processing in Preferences → Build → Compiler → Annotation Processors.
# @Data on entity causes StackOverflowError
Cause: @Data generates toString() that traverses the entire object graph on bidirectional relationships.
Fix: Replace @Data with explicit @Getter @Setter @NoArgsConstructor @AllArgsConstructor on JPA entities.
Skip the setup entirely
If you want a Spring Boot project with MapStruct and Lombok already correctly configured — pom.xml, annotation processor order, lombok-mapstruct-binding, entity, DTO, and mapper for each of your resources — SpringBoot Generator produces all of it in under 2 minutes.
Get a working setup in 2 minutes. Define your entities, download the ZIP, run mvn spring-boot:run.