examples/_fragments/after-mapping/java.md
# @AfterMapping method (Java)
## Insert Point
Default method in mapper interface body.
## Code
```defaults
skip this fragment (only added when entity has non-owner OneToOne/OneToMany with mappedBy + sub-DTO)
```
### OneToMany association
```java
@org.mapstruct.AfterMapping
default void link{entityAttrNameCapitalized}(@org.mapstruct.MappingTarget {entityClassFqn} {entityParamName}) {
{entityParamName}.get{entityAttrNameCapitalized}().forEach({unpluralizedAttrName} -> {unpluralizedAttrName}.set{inverseAttributeNameCapitalized}({entityParamName}));
}
```
### OneToOne association
```java
@org.mapstruct.AfterMapping
default void link{entityAttrNameCapitalized}(@org.mapstruct.MappingTarget {entityClassFqn} {entityParamName}) {
{attrTypeEntityFqn} {entityAttrName} = {entityParamName}.get{entityAttrNameCapitalized}();
if ({entityAttrName} != null) {
{entityAttrName}.set{inverseAttributeNameCapitalized}({entityParamName});
}
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{entityAttrName}` | entity association attribute name | e.g. `children`, `address` |
| `{entityAttrNameCapitalized}` | capitalized | e.g. `Children`, `Address` |
| `{entityParamName}` | decapitalized entity short name | e.g. `order` |
| `{entityClassFqn}` | entity class FQN | — |
| `{inverseAttributeNameCapitalized}` | capitalized mappedBy attribute | e.g. `Parent`, `Order` |
| `{unpluralizedAttrName}` | unpluralized attr name (OneToMany only) | e.g. `child` |
| `{attrTypeEntityFqn}` | association target entity FQN (OneToOne only) | — |
examples/_fragments/after-mapping/kotlin.md
# @AfterMapping method (Kotlin)
## Insert Point
Function in mapper abstract class body.
## Code
```defaults
skip this fragment (only added when entity has non-owner OneToOne/OneToMany with mappedBy + sub-DTO)
```
### OneToMany association
```kotlin
@org.mapstruct.AfterMapping
fun link{entityAttrNameCapitalized}(@org.mapstruct.MappingTarget {entityParamName}: {entityClassFqn}) {
{entityParamName}.{entityAttrName}.forEach { it.{inverseAttributeName} = {entityParamName} }
}
```
### OneToOne association
```kotlin
@org.mapstruct.AfterMapping
fun link{entityAttrNameCapitalized}(@org.mapstruct.MappingTarget {entityParamName}: {entityClassFqn}) {
{entityParamName}.{entityAttrName}?.{inverseAttributeName} = {entityParamName}
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{entityAttrName}` | entity association attribute name | e.g. `children`, `address` |
| `{entityAttrNameCapitalized}` | capitalized | e.g. `Children`, `Address` |
| `{entityParamName}` | decapitalized entity short name | e.g. `order` |
| `{entityClassFqn}` | entity class FQN | — |
| `{inverseAttributeName}` | mappedBy attribute name | e.g. `parent`, `order` |
examples/_fragments/aggregate-ref-from/java.md
# mapFromAggregateReference helper (Java)
## Insert Point
Default method in mapper interface body.
## Code
```defaults
skip this fragment (only for Spring Data JDBC entities with AggregateReference attributes)
```
### When entity has AggregateReference attributes with ID sub-DTO type
```java
default <T, R> R mapFromAggregateReference(org.springframework.data.jdbc.core.mapping.AggregateReference<T, R> aggregateReference) {
if (aggregateReference == null) {
return null;
}
return aggregateReference.getId();
}
```
## Variables
None (generic helper method).
examples/_fragments/aggregate-ref-from/kotlin.md
# mapFromAggregateReference helper (Kotlin)
## Insert Point
Function in mapper abstract class body.
## Code
```defaults
skip this fragment (only for Spring Data JDBC entities with AggregateReference attributes)
```
### When entity has AggregateReference attributes with ID sub-DTO type
```kotlin
fun <T, R> mapFromAggregateReference(aggregateReference: org.springframework.data.jdbc.core.mapping.AggregateReference<T, R>): R {
return aggregateReference?.id
}
```
## Variables
None (generic helper method).
examples/_fragments/aggregate-ref-to/java.md
# mapToAggregateReference helper (Java)
## Insert Point
Default method in mapper interface body.
## Code
```defaults
skip this fragment (only for Spring Data JDBC entities with AggregateReference attributes)
```
### When entity has AggregateReference attributes with ID sub-DTO type
```java
default <T, R> org.springframework.data.jdbc.core.mapping.AggregateReference<T, R> mapToAggregateReference(R id) {
if (id == null) {
return null;
}
return org.springframework.data.jdbc.core.mapping.AggregateReference.to(id);
}
```
## Variables
None (generic helper method).
examples/_fragments/aggregate-ref-to/kotlin.md
# mapToAggregateReference helper (Kotlin)
## Insert Point
Function in mapper abstract class body.
## Code
```defaults
skip this fragment (only for Spring Data JDBC entities with AggregateReference attributes)
```
### When entity has AggregateReference attributes with ID sub-DTO type
```kotlin
fun <T, R> mapToAggregateReference(id: R): org.springframework.data.jdbc.core.mapping.AggregateReference<T, R> {
return id?.let(org.springframework.data.jdbc.core.mapping.AggregateReference::to)
}
```
## Variables
None (generic helper method).
examples/_fragments/context-class/java.md
# Context inner record + helper methods (Java)
## Insert Point
Inner record in mapper interface body + helper methods.
## Code
```defaults
skip this fragment (only when entity has AggregateReference attributes with non-ID sub-DTO type)
```
### Context inner record
```java
record {dtoName}LoadedContext({type1} {field1}, java.util.Collection<{type2}> {field2s}) {
}
```
Context helper methods are generated per aggregate reference attribute. Their exact form depends on whether the attribute is a collection, child of collection, etc. Each attribute generates one helper method that uses the context record fields.
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{dtoName}` | DTO short class name | e.g. `OrderDto` |
| `{fieldN}` | from attribute type class name, decapitalized | — |
| `{typeN}` | attribute type FQN | — |
examples/_fragments/context-class/kotlin.md
# Context inner data class + helper methods (Kotlin)
## Insert Point
Inner data class in mapper abstract class body + helper methods.
## Code
```defaults
skip this fragment (only when entity has AggregateReference attributes with non-ID sub-DTO type)
```
### Context inner data class
```kotlin
data class {dtoName}LoadedContext(val {field1}: {type1}?, val {field2s}: java.util.Collection<{type2}>)
```
Context helper methods are generated per aggregate reference attribute. Their exact form depends on whether the attribute is a collection, child of collection, etc.
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{dtoName}` | DTO short class name | e.g. `OrderDto` |
| `{fieldN}` | from attribute type class name, decapitalized | — |
| `{typeN}` | attribute type FQN | — |
examples/_fragments/custom-to-dto/java.md
# Custom toDto method (Java)
## Insert Point
Static method in custom mapper class body.
## Code
```defaults
Always generated for Custom mapper.
```
### Standard form
Extract every **direct** entity property into a local first,
then call the DTO constructor with the locals (and any chained accessors)
in positional order.
```java
public static {dtoClassFqn} {methodName}({entityClassFqn} {entityParamName}) {
{dtoFieldType1} {entityParamName}{field1Cap} = {entityParamName}.get{field1Cap}();
{dtoFieldType2} {entityParamName}{field2Cap} = {entityParamName}.get{field2Cap}();
{dtoClassFqn} {dtoParamName} = new {dtoClassFqn}(
{entityParamName}{field1Cap},
{entityParamName}{field2Cap}
);
return {dtoParamName};
}
```
### Local-variable rule
For **direct** property access (`entity.getName()`) extract a local. For
**chained** access through a flat ToOne association
(`entity.getType().getId()`) inline the expression directly into the
constructor call — do **not** introduce a `petType.id` local.
Example with one direct field and one flat field:
```java
public static PetDto toPetDto(Pet pet) {
String petName = pet.getName();
PetDto petDto = new PetDto(petName, pet.getType().getId());
return petDto;
}
```
If you would rather always extract locals (slightly more uniform, slightly
more lines) that is functionally identical and acceptable — but the canonical
form inlines the chained call.
### Java Record DTO
The `toDto` side does **not** depend on whether the DTO is a record vs
class — both are constructed via the canonical constructor. The same
extract-locals-then-construct shape works unchanged. (Records change only
the `toEntity` side, where DTO values are read via component accessors —
see `custom-to-entity/java.md`.)
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern `to${DTO_NAME}` | e.g. `toOrderDto`, `toPetDto` |
| `{entityParamName}` | decapitalized entity short name | e.g. `pet` |
| `{dtoParamName}` | decapitalized DTO short name | e.g. `petDto` |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{fieldNCap}` | capitalized field names for getter calls | — |
| `{dtoFieldTypeN}` | DTO field types | — |
examples/_fragments/custom-to-dto/kotlin.md
# Custom toDto method (Kotlin)
## Insert Point
Extension function in the mapper file (not inside a class).
## Code
```defaults
Always generated for Custom mapper.
```
```kotlin
fun {entityClassFqn}.{methodName}() = {dtoClassFqn}(
{field1} = this.{field1},
{field2} = this.{field2}
)
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern `to${DTO_NAME}` | e.g. `toOrderDto` |
| `{entityClassFqn}` | entity class FQN | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{fieldN}` | DTO attribute names | — |
examples/_fragments/custom-to-entity/java.md
# Custom toEntity method (Java)
## Insert Point
Static method in custom mapper class body.
## Code
```defaults
Always generated for Custom mapper.
```
### Standard form
Extract every DTO value into a local first, then construct
the entity and apply setters. This mirrors `custom-to-dto` and is the
canonical style. **Use this form by default.**
```java
public static {entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName}) {
{dtoFieldType1} {dtoParamName}{dtoField1Cap} = {dtoParamName}.{dtoField1Accessor};
{dtoFieldType2} {dtoParamName}{dtoField2Cap} = {dtoParamName}.{dtoField2Accessor};
{entityClassFqn} {entityParamName} = new {entityClassFqn}();
{entityParamName}.set{field1Cap}({dtoParamName}{dtoField1Cap});
{entityParamName}.set{field2Cap}({dtoParamName}{dtoField2Cap});
return {entityParamName};
}
```
### `{dtoFieldNAccessor}` — record vs class
- DTO is a regular **class** → use the getter: `getName()`, `getTypeId()`, …
- DTO is a Java **record** → use the **record component accessor**, no
`get` prefix: `name()`, `typeId()`, …
Use the bare-component form for records and the
`getX()` form for classes. The choice is determined statically
from the DTO declaration: a `public record PetDto(...)` always uses the
component-accessor form.
### Flat ToOne — JPA stub pattern
When the DTO contains **flat fields** from a ToOne association (e.g. only
the id, or id+name extracted from `Pet.type`), do NOT call a repository or
fetch the association. Build a **stub** of the association entity, set
ONLY the flat fields, and assign it. JPA treats the stub as a known-id
reference; loading the real row is the persistence layer's job.
```java
public static Pet toEntity(PetDto petDto) {
String petDtoName = petDto.getName();
PetType petType = new PetType();
petType.setId(petDto.getTypeId());
Pet pet = new Pet();
pet.setName(petDtoName);
pet.setType(petType);
return pet;
}
```
If multiple flat fields come from the same association (`typeId` and
`typeName`), set them on the same stub:
```java
PetType petType = new PetType();
petType.setId(petDto.getTypeId());
petType.setName(petDto.getTypeName());
pet.setType(petType);
```
### Java Record + flat ToOne (combined)
```java
public static Pet toEntity(PetDto petDto) {
String petDtoName = petDto.name();
PetType petType = new PetType();
petType.setId(petDto.typeId());
Pet pet = new Pet();
pet.setName(petDtoName);
pet.setType(petType);
return pet;
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `toEntity` |
| `{dtoParamName}` | decapitalized DTO short name | e.g. `petDto` |
| `{entityParamName}` | decapitalized entity short name | e.g. `pet` |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{fieldNCap}` | capitalized entity field names for setter | — |
| `{dtoFieldNCap}` | capitalized DTO field names (used in local var name) | — |
| `{dtoFieldNAccessor}` | `getX()` for class DTO, `x()` for record DTO | — |
| `{dtoFieldTypeN}` | DTO field types | — |
examples/_fragments/custom-to-entity/kotlin.md
# Custom toEntity method (Kotlin)
## Insert Point
Extension function in the mapper file (not inside a class).
## Code
```defaults
Always generated for Custom mapper.
```
```kotlin
fun {dtoClassFqn}.{methodName}() = {entityClassFqn}().also {
it.{field1} = this.{field1}
it.{field2} = this.{field2}
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `toEntity` |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{fieldN}` | attribute names | — |
examples/_fragments/custom-update-with-null/java.md
# Custom updateWithNull method (Java)
## Insert Point
Static method in custom mapper class body.
## Code
```defaults
skip by default (only generated when user explicitly requests full update method)
```
### When UPDATE_WITH_NULL_VALUES requested
```java
public static {entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName}, {entityClassFqn} {entityParamName}) {
{entityParamName}.set{field1Cap}({dtoParamName}.get{dtoField1Cap}());
{entityParamName}.set{field2Cap}({dtoParamName}.get{dtoField2Cap}());
return {entityParamName};
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `updateWithNull` |
| `{dtoParamName}` | decapitalized DTO short name | — |
| `{entityParamName}` | decapitalized entity short name | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{fieldNCap}` | capitalized entity field names | — |
| `{dtoFieldNCap}` | capitalized DTO field names | — |
examples/_fragments/custom-update-with-null/kotlin.md
# Custom updateWithNull method (Kotlin)
## Insert Point
Extension function in the mapper file (not inside a class).
## Code
```defaults
skip by default (only generated when user explicitly requests full update method)
```
### When UPDATE_WITH_NULL_VALUES requested
```kotlin
fun {entityClassFqn}.{methodName}({dtoParamName}: {dtoClassFqn}) = apply {
{field1} = {dtoParamName}.{field1}
{field2} = {dtoParamName}.{field2}
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `updateWithNull` |
| `{dtoParamName}` | decapitalized DTO short name | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{fieldN}` | attribute names | — |
examples/_fragments/factory-field/java.md
# MAPPER factory field (Java)
## Insert Point
Field inside the mapper interface body. Only when componentModel = DEFAULT.
## Code
```defaults
skip this fragment (componentModel is SPRING by default)
```
### When componentModel = DEFAULT
```java
{className} MAPPER = org.mapstruct.factory.Mappers.getMapper({className}.class);
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{className}` | user choice | `{EntityName}Mapper` |
examples/_fragments/factory-field/kotlin.md
# MAPPER factory field (Kotlin)
## Insert Point
Companion object inside the mapper abstract class body. Only when componentModel = DEFAULT.
## Code
```defaults
skip this fragment (componentModel is SPRING by default)
```
### When componentModel = DEFAULT
```kotlin
companion object {
@kotlin.jvm.JvmStatic
val MAPPER: {className} = org.mapstruct.factory.Mappers.getMapper({className}::class.java)
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{className}` | user choice | `{EntityName}Mapper` |
examples/_fragments/flat-expression/java.md
# Flat expression method (Java)
## Insert Point
Default method in mapper interface body. Added together with the
corresponding `@Mapping(target=..., expression="java(...)")` line on
`toDto` (see `examples/_fragments/to-dto-method/java.md`).
## ⚠ Scope — collections only
This fragment applies **only** to flat **ToMany** fields (`List<Integer>
petIds`, `Set<Integer> specialtyIds`, etc.). For flat **ToOne** fields
(`Integer typeId`, `String typeName` flattened from `Pet.type`) do **not**
emit a helper method or `expression = ...`. Instead, use MapStruct
**dot-notation** in the regular `@Mapping(source, target)` annotation —
see `examples/_fragments/to-entity-method/java.md` ("With @Mapping
dot-notation for ToOne flat fields"). MapStruct generates the intermediate
`new PetType(); petType.setId(...);` itself.
## Code
```defaults
skip this fragment (only for DTOs with flat **collection** attributes where subDtoType == FLAT)
```
### Flat collection mapping method
```java
default {collectionFqn}<{attrDtoTypeFqn}> {functionName}(java.util.Collection<{mappedEntityFqn}> {assocFieldName}) {
return {assocFieldName}.stream().map({mappedEntityName}::get{mappedAttrNameCap}){aggregateReferenceMapper}{toCollectionExpression};
}
```
The parameter type is always `java.util.Collection` — not the concrete
collection type from the entity field. The method only calls `.stream()`,
so the concrete type is irrelevant. This also avoids mismatches when the
entity getter returns a different collection type than the field declaration
(e.g. field is `Set<Specialty>` but getter returns `List<Specialty>`).
Where `{toCollectionExpression}`:
- `.toList()` for List on JDK 16+
- `.collect(java.util.stream.Collectors.toList())` for List on JDK < 16
- `.collect(java.util.stream.Collectors.toSet())` for Set
Where `{aggregateReferenceMapper}`:
- `.map(org.springframework.data.jdbc.core.mapping.AggregateReference::getId)` if mapped attr is AggregateReference
- empty otherwise
## Naming rule (CRITICAL)
The helper function name is built from the entity association field name
and the flat DTO field name — **not** from the nested attribute name. This
guarantees uniqueness even when the same association is flattened in
multiple ways:
```
{functionName} = {assocFieldName} + "To" + capitalize({flatDtoFieldName})
```
For example, if `Owner.pets` is flattened twice into `petIds` and
`petNames`, you get two distinct helpers `petsToPetIds` and `petsToPetNames`.
A naming scheme based on the nested attribute (`petsToId`, `petsToName`)
would NOT collide here either, but it does collide as soon as two
different flat fields use the same nested attribute on different
associations — so use the DTO-field-based scheme unconditionally.
## Worked examples
```java
// Vet.specialties: Set<Specialty> -> VetDto.specialtyIds: Set<Integer>
default Set<Integer> specialtiesToSpecialtyIds(Collection<Specialty> specialties) {
return specialties.stream().map(Specialty::getId).collect(java.util.stream.Collectors.toSet());
}
// Owner.pets: List<Pet> -> OwnerDto.petIds: List<Integer> (JDK 16+)
default List<Integer> petsToPetIds(Collection<Pet> pets) {
return pets.stream().map(Pet::getId).toList();
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{functionName}` | `{assocFieldName} + "To" + capitalize({flatDtoFieldName})` | e.g. `specialtiesToSpecialtyIds`, `petsToPetIds` |
| `{assocFieldName}` | name of the entity association field (the part of the unFlatName before `.`); also used as the parameter name | e.g. `specialties`, `pets` |
| `{flatDtoFieldName}` | name of the flattened collection field on the DTO (the `target` of the `@Mapping`) | e.g. `specialtyIds`, `petIds` |
| `{mappedEntityFqn}` | association target entity FQN | — |
| `{mappedEntityName}` | short entity name for method reference | e.g. `Specialty`, `Pet` |
| `{mappedAttrNameCap}` | capitalized attribute name after `.` in unFlatName, used in `::get…` | e.g. `Id` |
| `{collectionFqn}` | `java.util.List` or `java.util.Set` (used for return type only; parameter is always `java.util.Collection`) | — |
| `{attrDtoTypeFqn}` | DTO field element type FQN | e.g. `java.lang.Integer` |
examples/_fragments/flat-expression/kotlin.md
# Flat expression method (Kotlin)
## Insert Point
Function in mapper abstract class body. Added via postProcess.
## Code
```defaults
skip this fragment (only for DTOs with flat collection attributes where subDtoType == FLAT)
```
### Flat collection mapping function
```kotlin
fun {functionName}({mappedFieldName}: Collection<{mappedEntityFqn}>): {collectionFqn}<{attrKotlinType}?> {
return {mappedFieldName}.mapNotNull{ it.{mappedAttrName} }{aggregateReferenceMapper}.toMutable{collectionClassName}()
}
```
The parameter type is always `Collection` — not the concrete collection
type from the entity field. The method only iterates, so the concrete
type is irrelevant. This also avoids mismatches when the entity getter
returns a different collection type than the field declaration.
Where `{aggregateReferenceMapper}`:
- `.map { it.id }` if mapped attr is AggregateReference
- empty otherwise
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{functionName}` | `{mappedFieldName}To{capitalize(attrName)}` | — |
| `{mappedFieldName}` | from unFlatName before `.` | — |
| `{mappedEntityFqn}` | association target entity FQN | — |
| `{mappedAttrName}` | attribute name after `.` | — |
| `{collectionFqn}` | `kotlin.collections.MutableList` or `kotlin.collections.MutableSet` | — |
| `{collectionClassName}` | `List` or `Set` | — |
| `{attrKotlinType}` | DTO field Kotlin type | — |
examples/_fragments/full-update-method/java.md
# updateWithNull MapStruct method (Java)
## Insert Point
Abstract method in mapper interface body.
## Code
```defaults
skip by default (only generated when user explicitly requests full update method)
```
### When UPDATE_WITH_NULL_VALUES requested
```java
@org.mapstruct.InheritConfiguration(name = "{firstUpdateMethodName}")
{entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName}, @org.mapstruct.MappingTarget {entityClassFqn} {entityParamName});
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `updateWithNull` |
| `{firstUpdateMethodName}` | name of first update/toEntity method | `toEntity` |
| `{dtoParamName}` | decapitalized DTO short name | — |
| `{entityParamName}` | decapitalized entity short name | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
examples/_fragments/full-update-method/kotlin.md
# updateWithNull MapStruct method (Kotlin)
## Insert Point
Abstract function in mapper abstract class body.
## Code
```defaults
skip by default (only generated when user explicitly requests full update method)
```
### When UPDATE_WITH_NULL_VALUES requested
```kotlin
@org.mapstruct.InheritConfiguration(name = "{firstUpdateMethodName}")
abstract fun {methodName}({dtoParamName}: {dtoClassFqn}, @org.mapstruct.MappingTarget {entityParamName}: {entityClassFqn}): {entityClassFqn}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `updateWithNull` |
| `{firstUpdateMethodName}` | name of first update/toEntity method | `toEntity` |
| `{dtoParamName}` | decapitalized DTO short name | — |
| `{entityParamName}` | decapitalized entity short name | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
examples/_fragments/mapper-annotation/java.md
# @Mapper annotation (Java)
## Insert Point
Annotation on the mapper interface created from skeleton.
## Code
```defaults
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING)
```
### SPRING component model (MappingConstants available)
```java
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING)
```
### SPRING component model (MappingConstants NOT available, older MapStruct)
```java
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = "spring")
```
### CDI component model
```java
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = "cdi")
```
### DEFAULT component model (no Spring, no CDI)
```java
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE)
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| componentModel | SPRING if any `spring-boot-starter*` / `spring-context` in `presentDeps`, else DEFAULT. CDI only when the user explicitly asked for it. | SPRING |
## Note — recommended default
Prefer the SPRING (modern) variant
(`MappingConstants.ComponentModel.SPRING`) whenever Spring is on the
classpath. The DEFAULT and CDI variants are listed for completeness
but are rarely needed in practice.
examples/_fragments/mapper-annotation/kotlin.md
# @Mapper annotation (Kotlin)
## Insert Point
Annotation on the mapper abstract class created from skeleton.
## Code
```defaults
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING)
```
### SPRING component model (MappingConstants available)
```kotlin
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING)
```
### SPRING component model (MappingConstants NOT available)
```kotlin
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = "spring")
```
### CDI component model
```kotlin
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = "cdi")
```
### DEFAULT component model
```kotlin
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE)
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| componentModel | SPRING if any `spring-boot-starter*` / `spring-context` in `presentDeps`, else DEFAULT. CDI only when the user explicitly asked for it. | SPRING |
examples/_fragments/parent-interface/java.md
# Parent interface extends clause (Java)
## Insert Point
Modifies the interface declaration to add extends clause.
## Code
```defaults
skip this fragment (no parent interface by default)
```
### When parent interface selected
```java
public interface {className} extends {parentFqn}<{dtoClassFqn}, {entityClassFqn}> {
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{className}` | user choice | `{EntityName}Mapper` |
| `{parentFqn}` | user choice | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
examples/_fragments/parent-interface/kotlin.md
# Parent interface supertypes clause (Kotlin)
## Insert Point
Modifies the abstract class declaration to add supertype.
## Code
```defaults
skip this fragment (no parent interface by default)
```
### When parent interface selected
```kotlin
abstract class {className} : {parentFqn}<{dtoClassFqn}, {entityClassFqn}>() {
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{className}` | user choice | `{EntityName}Mapper` |
| `{parentFqn}` | user choice | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
examples/_fragments/partial-update-method/java.md
# partialUpdate MapStruct method (Java)
## Insert Point
Abstract method in mapper interface body.
## Code
```defaults
skip by default (only generated when user explicitly requests partial update method)
```
### When PARTIAL_UPDATE requested
Always **one** method, controlled by ONE `@BeanMapping` annotation. The
`nullValuePropertyMappingStrategy` enum value is selected by the user
(SET_TO_NULL / IGNORE / SET_TO_DEFAULT). Do **not** emit
`@InheritConfiguration` — the inherited
mappings are not needed for the canonical Pet/Owner-style DTOs.
```java
@org.mapstruct.BeanMapping(nullValuePropertyMappingStrategy = org.mapstruct.NullValuePropertyMappingStrategy.{strategyEnum})
{entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName}, @org.mapstruct.MappingTarget {entityClassFqn} {entityParamName});
```
### Worked examples (one per strategy — only one is emitted)
```java
// SET_TO_NULL (the most common — null in DTO clears the field)
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL)
Owner partialUpdate(OwnerDto ownerDto, @MappingTarget Owner owner);
// IGNORE (null in DTO keeps the existing value)
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
Owner partialUpdate(OwnerDto ownerDto, @MappingTarget Owner owner);
// SET_TO_DEFAULT (null in DTO resets to type default — 0 for int, "" for String, etc.)
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
Owner partialUpdate(OwnerDto ownerDto, @MappingTarget Owner owner);
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `partialUpdate` |
| `{strategyEnum}` | user choice | `SET_TO_NULL` |
| `{dtoParamName}` | decapitalized DTO short name | e.g. `orderDto` |
| `{entityParamName}` | decapitalized entity short name | e.g. `order` |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
examples/_fragments/partial-update-method/kotlin.md
# partialUpdate MapStruct method (Kotlin)
## Insert Point
Abstract function in mapper abstract class body.
## Code
```defaults
skip by default (only generated when user explicitly requests partial update method)
```
### When PARTIAL_UPDATE requested
```kotlin
@org.mapstruct.BeanMapping(nullValuePropertyMappingStrategy = org.mapstruct.NullValuePropertyMappingStrategy.IGNORE)
@org.mapstruct.InheritConfiguration(name = "{toEntityMethodName}")
abstract fun {methodName}({dtoParamName}: {dtoClassFqn}, @org.mapstruct.MappingTarget {entityParamName}: {entityClassFqn}): {entityClassFqn}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `partialUpdate` |
| `{dtoParamName}` | decapitalized DTO short name | e.g. `orderDto` |
| `{entityParamName}` | decapitalized entity short name | e.g. `order` |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{toEntityMethodName}` | name of toEntity method | `toEntity` |
examples/_fragments/to-dto-method/java.md
# toDto MapStruct method (Java)
## Insert Point
Abstract method in mapper interface body, after toEntity method.
## Code
```defaults
Always generated by default.
```
### Without any annotations (all fields map directly) — preferred when applicable
```java
{dtoClassFqn} {methodName}({entityClassFqn} {entityParamName});
```
### With @Mapping annotations (default whenever any field needs remapping)
For each entity-field/DTO-field pair that requires a non-default mapping,
emit a separate `@Mapping` line. Use this even when `toEntity` already has
the inverse — see `references/mapping-annotations.md` for why.
```java
@org.mapstruct.Mapping(source = "{entityFieldName}", target = "{dtoFieldName}")
{dtoClassFqn} {methodName}({entityClassFqn} {entityParamName});
```
### With @Mapping(expression) for flat collection fields
Used together with the helper from `examples/_fragments/flat-expression/java.md`.
For each flat collection DTO field, add one of these `@Mapping` lines on
`toDto`. The helper method itself is added to the same mapper interface as
a `default` method.
```java
@org.mapstruct.Mapping(target = "{flatDtoFieldName}", expression = "java({functionName}({entityParamName}.get{AssocFieldCap}()))")
{dtoClassFqn} {methodName}({entityClassFqn} {entityParamName});
```
Where:
- `{flatDtoFieldName}` — the flat collection field on the DTO (e.g. `specialtyIds`, `petIds`)
- `{functionName}` — the helper method name from the flat-expression fragment (e.g. `specialtiesToSpecialtyIds`, `petsToPetIds`)
- `{AssocFieldCap}` — capitalized association field name on the entity (e.g. `Specialties`, `Pets`)
### With @Context parameter (aggregate references with context)
```java
{dtoClassFqn} {methodName}({entityClassFqn} {entityParamName}, @org.mapstruct.Context {dtoName}LoadedContext context);
```
### With @InheritInverseConfiguration — preferred when toEntity has 2+ flat ToOne mappings
When `toEntity` carries **only** invertible `@Mapping(source, target)`
pairs (the dot-notation flat-ToOne case from
`to-entity-method/java.md`), emit
`@InheritInverseConfiguration` on `toDto` instead of duplicating the
annotations. Use this form when:
- `toEntity` has at least one `@Mapping` and ALL of its mappings are plain
`(source, target)` pairs that MapStruct can invert losslessly (no
`expression`, `constant`, `defaultValue`, `ignore = true`, `qualifiedBy`,
or condition).
For the single-`@Mapping` case the duplication is so cheap that either form
is fine; default to inheritance only when there are 2+ mappings, which is
where the benefit becomes visible.
```java
@org.mapstruct.InheritInverseConfiguration(name = "{toEntityMethodName}")
{dtoClassFqn} {methodName}({entityClassFqn} {entityParamName});
```
Worked example (paired with the `Pet toEntity(PetDto)` from
`to-entity-method/java.md`):
```java
@InheritInverseConfiguration(name = "toEntity")
PetDto toPetDto(Pet pet);
```
If `toEntity` mixes invertible and non-invertible mappings (`expression`,
`ignore = true`, etc.), do **not** use inheritance — fall back to the
explicit `@Mapping` form documented above. See
`references/mapping-annotations.md` for the failure modes.
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern `to${DTO_NAME}` | e.g. `toOrderDto` |
| `{entityParamName}` | decapitalized entity short name | e.g. `order` |
| `{entityClassFqn}` | entity class FQN | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{toEntityMethodName}` | name of toEntity method | `toEntity` |
| `{entityFieldName}` | entity field that differs from DTO field | — |
| `{dtoFieldName}` | DTO field that differs from entity field | — |
| `{dtoName}` | DTO short class name | e.g. `OrderDto` |
examples/_fragments/to-dto-method/kotlin.md
# toDto MapStruct method (Kotlin)
## Insert Point
Abstract function in mapper abstract class body, after toEntity function.
## Code
```defaults
Always generated by default.
```
### With @InheritInverseConfiguration (when toEntity was created first and has mappings)
```kotlin
@org.mapstruct.InheritInverseConfiguration(name = "{toEntityMethodName}")
abstract fun {methodName}({entityParamName}: {entityClassFqn}): {dtoClassFqn}
```
### With @Mappings annotations (when toEntity was not created or has no mappings)
```kotlin
@org.mapstruct.Mappings(
org.mapstruct.Mapping(source = "{entityFieldName}", target = "{dtoFieldName}")
)
abstract fun {methodName}({entityParamName}: {entityClassFqn}): {dtoClassFqn}
```
### With @Context parameter (aggregate references with context)
```kotlin
abstract fun {methodName}({entityParamName}: {entityClassFqn}, @org.mapstruct.Context context: {dtoName}LoadedContext): {dtoClassFqn}
```
### Without any annotations (all fields map directly)
```kotlin
abstract fun {methodName}({entityParamName}: {entityClassFqn}): {dtoClassFqn}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern `to${DTO_NAME}` | e.g. `toOrderDto` |
| `{entityParamName}` | decapitalized entity short name | e.g. `order` |
| `{entityClassFqn}` | entity class FQN | — |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{toEntityMethodName}` | name of toEntity method | `toEntity` |
| `{entityFieldName}` | entity field that differs from DTO field | — |
| `{dtoFieldName}` | DTO field that differs from entity field | — |
| `{dtoName}` | DTO short class name | e.g. `OrderDto` |
examples/_fragments/to-entity-method/java.md
# toEntity MapStruct method (Java)
## Insert Point
Abstract method in mapper interface body.
## Code
```defaults
Always generated by default.
```
### With @Mapping annotations (first method, or no inverse available)
```java
@org.mapstruct.Mapping(source = "{dtoFieldName}", target = "{entityFieldName}")
{entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName});
```
### With @Mapping dot-notation for ToOne flat fields — preferred for ToOne FLAT
When DTO has one or more flat fields from a single ToOne association (e.g.
`PetDto` with `typeId` / `typeName` flattened from `Pet.type`), emit one
`@Mapping` per flat field using **dot-notation** in `target`. No
Java-expression, no helper method. MapStruct itself synthesizes the
intermediate `new PetType()` and the field setters.
```java
@org.mapstruct.Mapping(source = "{flatDtoFieldName}", target = "{assocFieldName}.{nestedFieldName}")
@org.mapstruct.Mapping(source = "{flatDtoFieldName2}", target = "{assocFieldName}.{nestedFieldName2}")
{entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName});
```
Worked example — `PetDto(typeId, typeName)` flattens `Pet.type` (which
points at `PetType`):
```java
@Mapping(source = "typeName", target = "type.name")
@Mapping(source = "typeId", target = "type.id")
Pet toEntity(PetDto petDto);
```
Note: the order of `@Mapping` annotations does not matter functionally.
Any consistent ordering is acceptable.
### With @InheritInverseConfiguration (when toDto was created first)
```java
@org.mapstruct.InheritInverseConfiguration(name = "{inverseMethodName}")
{entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName});
```
### Without any annotations (all fields map directly, no remapping needed)
```java
{entityClassFqn} {methodName}({dtoClassFqn} {dtoParamName});
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `toEntity` |
| `{dtoParamName}` | decapitalized DTO short name | e.g. `orderDto` |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{dtoFieldName}` | DTO field that differs from entity field | — |
| `{entityFieldName}` | entity field that differs from DTO field | — |
| `{inverseMethodName}` | name of toDto method | e.g. `toOrderDto` |
examples/_fragments/to-entity-method/kotlin.md
# toEntity MapStruct method (Kotlin)
## Insert Point
Abstract function in mapper abstract class body.
## Code
```defaults
Always generated by default.
```
### With @Mappings annotations (first method, or no inverse available)
```kotlin
@org.mapstruct.Mappings(
org.mapstruct.Mapping(source = "{dtoFieldName}", target = "{entityFieldName}")
)
abstract fun {methodName}({dtoParamName}: {dtoClassFqn}): {entityClassFqn}
```
### With @InheritInverseConfiguration (when toDto was created first)
```kotlin
@org.mapstruct.InheritInverseConfiguration(name = "{inverseMethodName}")
abstract fun {methodName}({dtoParamName}: {dtoClassFqn}): {entityClassFqn}
```
### Without any annotations (all fields map directly)
```kotlin
abstract fun {methodName}({dtoParamName}: {dtoClassFqn}): {entityClassFqn}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{methodName}` | naming pattern | `toEntity` |
| `{dtoParamName}` | decapitalized DTO short name | e.g. `orderDto` |
| `{dtoClassFqn}` | DTO class FQN | — |
| `{entityClassFqn}` | entity class FQN | — |
| `{dtoFieldName}` | DTO field that differs from entity field | — |
| `{entityFieldName}` | entity field that differs from DTO field | — |
| `{inverseMethodName}` | name of toDto method | e.g. `toOrderDto` |
examples/_fragments/uses-attribute/java.md
# @Mapper(uses = ...) attribute (Java)
## Insert Point
Modifies existing @Mapper annotation to add uses attribute.
## Code
```defaults
skip this fragment (no sub-mappers by default)
```
### When sub-mappers exist for association DTOs
```java
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = ..., uses = {SubMapper1.class, SubMapper2.class})
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| sub-mapper FQNs | auto-detected from existing mappers for association entities | — |
examples/_fragments/uses-attribute/kotlin.md
# @Mapper(uses = ...) attribute (Kotlin)
## Insert Point
Modifies existing @Mapper annotation to add uses attribute.
## Code
```defaults
skip this fragment (no sub-mappers by default)
```
### When sub-mappers exist for association DTOs
```kotlin
@org.mapstruct.Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE, componentModel = ..., uses = [SubMapper1::class, SubMapper2::class])
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| sub-mapper FQNs | auto-detected from existing mappers for association entities | — |
examples/_skeletons/custom-java.md
# Custom mapper class (Java)
## Code
```java
package {packageName};
public class {className} {
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{packageName}` | project context | — |
| `{className}` | user choice | `{EntityName}Mapper` |
examples/_skeletons/custom-kotlin.md
# Custom mapper file (Kotlin)
## Code
```kotlin
package {packageName}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{packageName}` | project context | — |
examples/_skeletons/mapstruct-java.md
# MapStruct mapper interface (Java)
## Code
```java
package {packageName};
public interface {className} {
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{packageName}` | project context | — |
| `{className}` | user choice | `{EntityName}Mapper` |
examples/_skeletons/mapstruct-kotlin.md
# MapStruct mapper abstract class (Kotlin)
## Code
```kotlin
package {packageName}
abstract class {className} {
}
```
## Variables
| Variable | Source | Default |
|----------|--------|---------|
| `{packageName}` | project context | — |
| `{className}` | user choice | `{EntityName}Mapper` |
references/custom-java.md
# Custom Mapper -- Java
## Skeleton
Read `examples/_skeletons/custom-java.md`. Write the file to `src/main/java/{packagePath}/{className}.java`.
## Generation order
1. Create file from skeleton (plain class)
2. Add toDto static method from `examples/_fragments/custom-to-dto/java.md`
- For each DTO field: extract value from entity via getter into a local variable
- Create DTO via constructor call with all extracted variables
- Return the DTO
3. Add toEntity static method from `examples/_fragments/custom-to-entity/java.md`
- For each direct DTO field: extract value into a local using the DTO accessor (`getX()` for class DTOs, `x()` for record DTOs)
- Create new entity instance
- Call entity setters with the locals
- For flat ToOne fields: build a JPA stub of the association entity (`new PetType(); petType.setId(...);`) and pass it to the parent setter
- Return the entity
4. If UPDATE_WITH_NULL_VALUES requested: add from `examples/_fragments/custom-update-with-null/java.md`
- Takes both DTO and entity as parameters
- Calls entity setters with DTO getter values
- Returns the entity
## Method body construction
**toDto method:**
- Init expressions: `{DtoFieldType} {entityParam}{FieldCap} = {entityParam}.get{FieldCap}();` for each DTO field
- Result: `new {DtoClassFqn}({entityParam}{Field1Cap}, {entityParam}{Field2Cap}, ...)`
**toEntity method:**
- For each direct DTO field: `{DtoFieldType} {dtoParam}{DtoFieldCap} = {dtoParam}.{accessor};`
- `{accessor}` = `get{DtoFieldCap}()` for class DTO, `{dtoFieldName}()` for record DTO
- Init: `{EntityClassFqn} {entityParam} = new {EntityClassFqn}();`
- For each direct field: `{entityParam}.set{FieldCap}({dtoParam}{DtoFieldCap});`
- For each flat ToOne assoc with fields {f1, f2, ...} from `{Assoc}`:
- `{Assoc} {assocVar} = new {Assoc}();`
- `{assocVar}.set{F1Cap}({dtoParam}.{f1Accessor});`
- `{assocVar}.set{F2Cap}({dtoParam}.{f2Accessor});`
- `{entityParam}.set{AssocCap}({assocVar});`
- Return: `{entityParam}`
**updateWithNull method:**
- Body: same setter pattern as toEntity, but entity is a parameter (not new)
- Return: `{entityParam}`
## Method naming
Same defaults as MapStruct variant (see `references/mapstruct-java.md`).
references/custom-kotlin.md
# Custom Mapper -- Kotlin
## Skeleton
Read `examples/_skeletons/custom-kotlin.md`. Write the file to `src/main/kotlin/{packagePath}/{className}.kt`.
Note: Kotlin custom mapper is a **file with extension functions**, not a class.
## Generation order
1. Create file from skeleton (just package declaration)
2. Add toDto extension function from `examples/_fragments/custom-to-dto/kotlin.md`
- Extension on entity class
- Returns DTO via named constructor arguments: `DtoFqn(field1 = this.field1, field2 = this.field2)`
3. Add toEntity extension function from `examples/_fragments/custom-to-entity/kotlin.md`
- Extension on DTO class
- Returns `EntityFqn().also { it.field1 = this.field1; ... }`
4. If UPDATE_WITH_NULL_VALUES requested: add from `examples/_fragments/custom-update-with-null/kotlin.md`
- Extension on entity class with DTO parameter
- Uses `apply { field1 = dtoParam.field1; ... }`
## Kotlin-specific patterns
- **toDto**: `fun EntityFqn.toOrderDto() = DtoFqn(field1 = this.field1, field2 = this.field2)`
- **toEntity**: `fun DtoFqn.toEntity() = EntityFqn().also { it.field1 = this.field1; it.field2 = this.field2 }`
- **updateWithNull**: `fun EntityFqn.updateWithNull(orderDto: DtoFqn) = apply { field1 = orderDto.field1; ... }`
## Method naming
Same defaults as MapStruct variant (see `references/mapstruct-java.md`).
references/mapping-annotations.md
# @Mapping Annotation Rules
## Determining field mappings
Compare entity fields (from `get_entity_details`) with DTO fields (from `list_class_members`).
For each DTO field, determine the mapping type:
### Direct match
Same name and compatible type in both entity and DTO. No `@Mapping` annotation needed.
### Different name
DTO field name differs from entity field name.
```java
@org.mapstruct.Mapping(source = "{sourceField}", target = "{targetField}")
```
### SubDtoType = ID (association mapped to ID only)
DTO has an ID field for an entity association (e.g. `Long customerId` for `Customer customer`).
```java
@org.mapstruct.Mapping(source = "{dtoIdField}", target = "{entityAssocField}.id")
```
For toDto (reverse): `@Mapping(source = "{entityAssocField}.id", target = "{dtoIdField}")`
### SubDtoType = FLAT (flattened association fields)
DTO has individual fields extracted from a nested entity (e.g. `String customerName` from `customer.name`).
- For **non-collection (ToOne)**: dot-notation in BOTH directions. Note that on `toEntity` the dot is on the **target** side, on `toDto` it's on the **source** side. MapStruct synthesizes the intermediate `new {Assoc}()` itself.
- toEntity: `@Mapping(source = "{dtoFlatField}", target = "{entityAssocField}.{nestedField}")`
- toDto: `@Mapping(source = "{entityAssocField}.{nestedField}", target = "{dtoFlatField}")` — or use `@InheritInverseConfiguration` (see below) when there are 2+ flat mappings.
- For **collection (ToMany)**: uses `expression` with helper method (see flat-expression fragments). Dot-notation does not work for collections.
### SubDtoType = NEW_CLASS / NEW_NESTED_CLASS / EXIST_CLASS
DTO uses another DTO for the association. **MapStruct handles this implicitly** by generating private nested-mapping methods inside the same mapper interface — no `uses=` and no separate sub-mapper file are needed by default. Do not emit `uses` for these cases — mapping will be handled implicitly. Add `uses` only when a real sibling mapper must be reused or a `@Named` qualifier is required.
## @InheritInverseConfiguration — opt-in only, NOT the default
When toEntity is generated first (always true by default), it is technically
possible for toDto to use:
```java
@org.mapstruct.InheritInverseConfiguration(name = "toEntity")
```
This inherits all `@Mapping` annotations in reverse, avoiding duplication.
**However, this is NOT the default in this skill.** Reasons to prefer
duplicated `@Mapping(source, target)` pairs on toDto:
1. **Robust under refactoring.** `@InheritInverseConfiguration` silently
breaks (or starts producing wrong code) the moment `toEntity` gains any
non-invertible mapping: `ignore = true`, `expression = "..."`,
`constant = "..."`, `defaultValue`, `qualifiedBy`, `dateFormat`,
`numberFormat`. None of these have a meaningful inverse.
2. **Locally readable.** A reviewer reading toDto sees its mappings inline;
they do not have to scroll up to toEntity and mentally invert each line.
3. **One fewer import** (`org.mapstruct.InheritInverseConfiguration`).
4. **Order-independent.** If a future edit reorders steps 5 and 6 of the
generation order, the duplicated `@Mapping` form keeps working;
`@InheritInverseConfiguration(name = "toEntity")` would resolve to a
forward reference.
**Exception — multi-flat-ToOne case.** When
`toEntity` carries **2+** plain `@Mapping(source, target)` annotations
(typically the dot-notation flat-ToOne case from
`to-entity-method/java.md`) and ALL of them are losslessly invertible,
prefer `@InheritInverseConfiguration(name = "toEntity")` on `toDto`.
Worked example:
```java
@Mapping(source = "typeName", target = "type.name")
@Mapping(source = "typeId", target = "type.id")
Pet toEntity(PetDto petDto);
@InheritInverseConfiguration(name = "toEntity")
PetDto toPetDto(Pet pet);
```
For the single-`@Mapping` case the duplication is so cheap that either
form is fine — default to duplication for refactor-safety. For 2+ mappings,
default to inheritance to reduce duplication.
## partialUpdate has its OWN @BeanMapping — no @InheritConfiguration
The `partialUpdate` method is generated independently and carries exactly
ONE annotation:
```java
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.{strategy})
{Entity} partialUpdate({Dto} dto, @MappingTarget {Entity} entity);
```
`{strategy}` is one of `SET_TO_NULL` (default) / `IGNORE` /
`SET_TO_DEFAULT`, picked by the user. **Do not** add
`@InheritConfiguration(name = "toEntity")` —
chasing inherited mappings here only creates surprising failures when
`toEntity` later acquires non-invertible mappings.
## Kotlin wrapping rule
In Kotlin, multiple `@Mapping` annotations must be wrapped:
```kotlin
@org.mapstruct.Mappings(value = [
org.mapstruct.Mapping(source = "field1", target = "field2"),
org.mapstruct.Mapping(source = "field3", target = "field4")
])
```
Single `@Mapping` can be used directly without wrapping.
references/mapstruct-java.md
# MapStruct Mapper -- Java
## Skeleton
Read `examples/_skeletons/mapstruct-java.md`. Write the file to `src/main/java/{packagePath}/{className}.java`.
## Generation order
1. Create file from skeleton
2. Add `@Mapper` annotation from `examples/_fragments/mapper-annotation/java.md`
- Determine componentModel: any `spring-boot-starter*` or `spring-context` in `presentDeps` → SPRING; otherwise → DEFAULT.
- CDI is **not** auto-detected. Use the CDI variant only when the user explicitly asked for `componentModel = "cdi"`.
- For SPRING, use `MappingConstants.ComponentModel.SPRING` form (modern MapStruct).
3. If componentModel = DEFAULT: add MAPPER factory field from `examples/_fragments/factory-field/java.md`
4. If user selected parent interface: modify declaration from `examples/_fragments/parent-interface/java.md`
5. Add toEntity method from `examples/_fragments/to-entity-method/java.md`
- For each DTO field that maps to a different entity field name, add `@Mapping(source, target)` annotation
- If DTO field has SubDtoType = ID (association ID only), add `@Mapping(source = "dtoField", target = "entityAssoc.id")`
- If DTO field has SubDtoType = FLAT for a **ToOne** association (single object, e.g. `typeId`/`typeName` from `Pet.type`):
emit one `@Mapping(source = "flatDtoField", target = "assoc.nestedField")` per flat field — **dot-notation, no helper method, no Java-expression**. MapStruct synthesizes the intermediate `new PetType()` and the setters itself.
- If DTO field has SubDtoType = FLAT for a **ToMany** association (collection), use the helper-method form from `examples/_fragments/flat-expression/java.md` (collections cannot use dot-notation).
6. Add toDto method from `examples/_fragments/to-dto-method/java.md`
- **Single `@Mapping`** on `toEntity` → emit the symmetric `@Mapping(source, target)` on `toDto`. Duplication is one cheap line and refactor-safe.
- **Two or more invertible `@Mapping`s** on `toEntity` (the typical multi-flat-ToOne case) → emit `@InheritInverseConfiguration(name = "{toEntityMethodName}")` on `toDto`. This removes the duplication that grows linearly with flat fields.
- **Mixed invertible / non-invertible mappings** on `toEntity` (any `expression`, `constant`, `defaultValue`, `ignore = true`, `qualifiedBy`, condition) → fall back to explicit duplicated `@Mapping`s on `toDto`. `@InheritInverseConfiguration` silently breaks in this case.
- If entity has AggregateReference attrs with non-ID sub-DTO, add `@Context` parameter
7. If PARTIAL_UPDATE requested: add from `examples/_fragments/partial-update-method/java.md`
- Pass `{strategyEnum}` from the user's `partialUpdateNullStrategy` answer (default `SET_TO_NULL`).
- Always emit ONE method with one `@BeanMapping(nullValuePropertyMappingStrategy = ...)` — never two methods, never `@InheritConfiguration`.
8. If UPDATE_WITH_NULL_VALUES requested: add from `examples/_fragments/full-update-method/java.md`
9. Check entity associations for @AfterMapping need:
- For each non-owner OneToOne or OneToMany with `mappedBy` where DTO uses sub-DTO (not ID, not FLAT):
- Add method from `examples/_fragments/after-mapping/java.md`
10. If entity has AggregateReference attrs with ID sub-DTO:
- Add `mapToAggregateReference` from `examples/_fragments/aggregate-ref-to/java.md`
- Add `mapFromAggregateReference` from `examples/_fragments/aggregate-ref-from/java.md`
11. If DTO has flat collection attributes (SubDtoType = FLAT + collection):
- Add flat expression method from `examples/_fragments/flat-expression/java.md`
- The toDto method gets `@Mapping(target = "flatField", expression = "java(functionName(entity.getAssoc()))")` instead of regular mapping
- **Known limitation — one-way only.** The flat collection helper is
generated only in the entity → DTO direction. The reverse
(`flatField` → `entityAssoc`) is **not** generated, and
`unmappedTargetPolicy = IGNORE` will silently leave the collection
empty on `toEntity`. Loading entities by id is the responsibility of
the calling service via the JPA repository — do **not** inject a
repository or `EntityManager` into the mapper. If the user needs the
reverse, point them at the service layer instead of trying to
generate it.
12. If entity has AggregateReference attrs with non-ID sub-DTO:
- Add context record + helper methods from `examples/_fragments/context-class/java.md`
13. **`uses = {...}` is rarely needed.** MapStruct generates private nested-mapping methods **implicitly** when both the source and target sub-types are visible to the same mapper interface. Do **not** emit `uses` for NEW_CLASS / NEW_NESTED_CLASS sub-DTOs — mapping will be handled implicitly.
- **Default: do not add `uses`.** Do not call `list_entity_mappers` and do not generate a separate sub-mapper file for the association.
- **Exceptions** that DO warrant `uses = {SomeMapper.class}`:
- the user explicitly asks for an existing sibling mapper to be reused (e.g. complex per-association logic already lives there);
- the association mapping needs a `@Named` qualifier from another mapper;
- circular dependencies between mappers that you cannot resolve via implicit generation.
- When you DO need it, use `examples/_fragments/uses-attribute/java.md`.
## @Mapping annotation rules
For each DTO field, determine mapping type based on entity-DTO field comparison:
- **Direct match** (same name and type): no `@Mapping` needed
- **Different name**: `@Mapping(source = "dtoFieldName", target = "entityFieldName")`
- **SubDtoType = ID**: `@Mapping(source = "dtoField", target = "entityAssoc.id")`
- **SubDtoType = FLAT**: `@Mapping(source = "dtoField", target = "assoc.nestedField")` or expression for collections
- **SubDtoType = NEW_CLASS/NEW_NESTED_CLASS/EXIST_CLASS**: MapStruct handles via sub-mapper in `uses`
## Method naming
Default patterns:
- toEntity: `"toEntity"`
- toDto: `"to{DtoShortName}"` (e.g. `toOrderDto`)
- partialUpdate: `"partialUpdate"`
- updateWithNull: `"updateWithNull"`
- Collection methods: same as single (LIKE_SINGLE strategy)
references/mapstruct-kotlin.md
# MapStruct Mapper -- Kotlin
## Skeleton
Read `examples/_skeletons/mapstruct-kotlin.md`. Write the file to `src/main/kotlin/{packagePath}/{className}.kt`.
## Generation order
1. Create file from skeleton
2. Add `@Mapper` annotation from `examples/_fragments/mapper-annotation/kotlin.md`
- Determine componentModel: any `spring-boot-starter*` or `spring-context` in `presentDeps` → SPRING; otherwise → DEFAULT.
- CDI is **not** auto-detected. Use the CDI variant only when the user explicitly asked for `componentModel = "cdi"`.
3. If componentModel = DEFAULT: add MAPPER companion object from `examples/_fragments/factory-field/kotlin.md`
4. If user selected parent interface: modify declaration from `examples/_fragments/parent-interface/kotlin.md`
5. Add toEntity method from `examples/_fragments/to-entity-method/kotlin.md`
- Multiple `@Mapping` annotations MUST be wrapped in `@Mappings(value = [...])`
- For each DTO field that maps to a different entity field, add `Mapping(source, target)` inside `@Mappings`
6. Add toDto method from `examples/_fragments/to-dto-method/kotlin.md`
- If toEntity was created and has `@Mapping` annotations, use `@InheritInverseConfiguration(name = "toEntity")`
- Otherwise add individual `@Mapping`/`@Mappings` annotations
- If entity has AggregateReference attrs with non-ID sub-DTO, add `@Context` parameter
7. If PARTIAL_UPDATE requested: add from `examples/_fragments/partial-update-method/kotlin.md`
8. If UPDATE_WITH_NULL_VALUES requested: add from `examples/_fragments/full-update-method/kotlin.md`
9. Check entity associations for @AfterMapping need:
- For each non-owner OneToOne or OneToMany with `mappedBy` where DTO uses sub-DTO:
- Add method from `examples/_fragments/after-mapping/kotlin.md`
10. If entity has AggregateReference attrs with ID sub-DTO:
- Add `mapToAggregateReference` from `examples/_fragments/aggregate-ref-to/kotlin.md`
- Add `mapFromAggregateReference` from `examples/_fragments/aggregate-ref-from/kotlin.md`
11. If DTO has flat collection attributes (SubDtoType = FLAT + collection):
- Add flat expression function from `examples/_fragments/flat-expression/kotlin.md`
12. If entity has AggregateReference attrs with non-ID sub-DTO:
- Add context data class + helper methods from `examples/_fragments/context-class/kotlin.md`
13. Add sub-mapper references to `@Mapper(uses = ...)` from `examples/_fragments/uses-attribute/kotlin.md`
- **Lazy fetch:** for each association entity used by the DTO via a sub-DTO type (NEW_CLASS / NEW_NESTED_CLASS / EXIST_CLASS), call `list_entity_mappers(associationEntityFqn)` **at this step** — not in Step 1 of `SKILL.md`. The call needs the association entity FQN, which is only known after `entityDetails` is fetched in Step 2.
- Add found mapper FQNs to the `uses` attribute. Skip associations with no existing mapper. If no association needs a sub-mapper, skip the `uses` attribute entirely.
## Kotlin-specific differences from Java
- Mapper is `abstract class`, not `interface`
- Methods are `abstract fun`, not interface abstract methods
- Helper methods (afterMapping, aggregateRef) are regular functions, not `default` methods
- Multiple `@Mapping` annotations MUST be wrapped: `@Mappings(value = [Mapping(...), Mapping(...)])`
- MAPPER factory uses `companion object` with `@JvmStatic`
- Parent interface uses `: ParentFqn<Dto, Entity>()` (with `()`)
- Custom extension functions use `this.field` access
- `@MappingTarget` goes before param name: `@MappingTarget entity: EntityFqn`
## @Mapping annotation rules
Same as Java (see `references/mapstruct-java.md`), but annotations wrapped in `@Mappings(value = [...])` when multiple.
## Method naming
Same as Java (see `references/mapstruct-java.md`).
references/method-naming.md
# Method Naming Conventions
## Default patterns
| Method type | Pattern | Example (Entity=Order, DTO=OrderDto) |
|-------------|---------|--------------------------------------|
| toEntity | `"toEntity"` | `toEntity` |
| toDto | `"to${DTO_NAME}"` | `toOrderDto` |
| partialUpdate | `"partialUpdate"` | `partialUpdate` |
| updateWithNull | `"updateWithNull"` | `updateWithNull` |
`${DTO_NAME}` is replaced with the DTO short class name (e.g. `OrderDto`).
## Parameter naming
| Parameter | Rule | Example |
|-----------|------|---------|
| entity param | decapitalized entity short name | `order` |
| DTO param | decapitalized DTO short name | `orderDto` |
## Collection method naming strategy
Default: LIKE_SINGLE (same name as single-item method).
Other strategies (configurable, but rarely changed):
- PLURALIZE: `toEntities`, `toOrderDtos`
- COLLECTION_TYPE: `toEntityList`, `toOrderDtoList`
## Custom extension function naming (Kotlin Custom mapper)
- toDto: `fun EntityFqn.toOrderDto()` -- extension on entity
- toEntity: `fun DtoFqn.toEntity()` -- extension on DTO
- updateWithNull: `fun EntityFqn.updateWithNull(orderDto: DtoFqn)` -- extension on entity with DTO param
SKILL.md
---
name: mapper-creator
description: >
Creates a mapper between an entity and a DTO (MapStruct or custom converter).
Use this skill when a mapper/converter between entity and DTO needs to be created,
either standalone or as part of a larger task (e.g. after DTO creation, during CRUD setup).
---
# Preflight: Spring MCP
This skill is part of the **Spring Agent Toolkit** and is designed to work with the **Spring MCP server** (provided by the Amplicode IntelliJ plugin). Before doing anything else, check your tool list for any Spring MCP tool — they are exposed under the `amplicode` MCP server (e.g. `get_project_summary`, `list_module_dependencies`, `get_entity_details`); harnesses that flatten MCP tools into the tool list use the `mcp__amplicode__` prefix on the same names.
- **If at least one Amplicode tool is available** — MCP is connected. Proceed with the skill below.
- **If none are available** — stop and invoke the **`amplicode-install`** skill (bundled with the Spring Agent Toolkit). It installs the Amplicode plugin and walks the user through the **«Настроить Spring Agent»** welcome-screen button + MCP-client restart. After it completes, the MCP tools become available — resume this skill.
- If `amplicode-install` is not registered in your skill list, tell the user (in their language): *"This skill needs the Amplicode IntelliJ plugin and its MCP server. Install it from https://amplicode.ru/marketplace into IntelliJ IDEA Ultimate/Community or GigaIDE, open any project, click «Настроить Spring Agent» on the Amplicode welcome screen, then restart your MCP client."*
---
# Mapper Creator
Creates a mapper (MapStruct interface/abstract class or custom converter) for converting between an entity and a DTO.
---
> **CRITICAL: Code ONLY from examples/ files. If no matching example -- STOP and ask user.**
> **CRITICAL: For questions with a fixed set of choices, prefer `AskUserQuestion` > its analogue > plain text list. Plain numbered text lists are the last resort when no interactive tool is available.**
> **CRITICAL: Read the conversation context BEFORE running Step 1.** Half the questions in Steps 2–3 may already be answered by the user's prompt and prior turns. Re-asking what was already said is the #1 reason this skill feels slow.
---
## Defaults
| Option | Default | Always ask? | Notes |
|--------|---------|-------------|-------|
| entity | — | YES | which entity to map |
| dtoClass | — | YES | which DTO to map to |
| mapperType | MapStruct | YES | main choice: MapStruct or Custom |
| className | `{EntityName}Mapper` | NO | suggest, confirm |
| packageName | package next to DTO | NO | auto-determined |
| parentInterface | null | NO | extend a common mapper interface |
| language | from `get_project_summary` | NO | auto-determined |
| componentModel | SPRING (if Spring is in dependencies), otherwise DEFAULT | NO | CDI is not auto-detected — user must specify explicitly |
| partialUpdate | no | NO | add partialUpdate method |
| partialUpdateNullStrategy | SET_TO_NULL | NO | if partialUpdate = yes: SET_TO_NULL / IGNORE / SET_TO_DEFAULT |
| updateWithNull | no | NO | add updateWithNull method |
| dtoIsRecord | false | NO | whether to use Java record for DTO (Java only; affects accessors in custom toEntity) |
**Smart defaults:** If the user says "use defaults", "all defaults", "default settings" -- skip all questions where "Always ask?" = NO. Ask only the required ones.
**Smart answer recognition:** When the user directly provides a value instead of choosing from a list -- accept it. Examples:
- Question "Which entity?" -> user answers "Order" -> that IS the entity
- Question "Mapper type?" -> user answers "mapstruct" -> that IS the choice
- If the user gave multiple answers in one message -> accept all, skip the answered questions
- NEVER re-ask what the user has already answered (even indirectly)
**Batch questions:** Group related questions into a single `AskUserQuestion` call (up to 4 questions):
- The main question (mapper type) is always asked SEPARATELY
- Do not group questions from DIFFERENT decision branches
- Prefer `AskUserQuestion`; fall back to plain text only if the tool is unavailable
---
## Step 0 -- Conversation context first (REQUIRED, no tool calls)
Before any MCP call, before any question, **re-read the user's prompt and
the prior turns of this conversation** and extract whatever is already
stated. This step costs nothing and prevents the most common failure mode
of this skill — asking the user something they already said.
Build a mental checklist of inputs and tick off everything the user has
already provided, explicitly or implicitly:
| Input | Look for in the prompt / context |
|---|---|
| **entity** | a class name (`Order`, `Vet`, `ScheduleTemplate`); "for X"; "from X to Y"; an open file in the IDE; a recently discussed entity |
| **DTO** | a class name ending in `Dto` / `Response` / `Request`; "to `OrderDto`"; "from X to Y"; a DTO that was just generated by `dto-creator` in this same conversation |
| **mapperType** | "MapStruct", "mapstruct", "@Mapper", "custom", "manually", "static methods", "extension function" → MapStruct vs Custom |
| **className** | "name it `OrderConverter`", "class `FooMapper`" |
| **package** | "in package `…`", "next to DTO", "next to controller" |
| **methods** | "only toDto", "with update", "partial update", "updateWithNull" |
| **smart defaults** | "use defaults", "all defaults", "default settings", "as usual" |
| **prior project facts** | language, JDK, dependencies — already known if discussed earlier in this conversation; do not re-fetch |
| **delegated invocation** | if `dto-creator` just delegated to this skill, the entity, DTO, package, and language are ALL known — never re-ask |
For every input that is **explicitly or strongly implicitly answered**:
mark it as decided and skip the corresponding question in Steps 2–3. Do
NOT ask "which entity?" if the user wrote "create a mapper for Order
to OrderDto" — both entity and DTO are answered. Do NOT ask "MapStruct
or Custom?" if the user wrote "create a MapStruct mapper".
For every input that is **not** answered: defer to the Decision-making
principle below — try to derive it from project context first (Step 1),
and only then ask.
Step 0 is mental, not a tool call. Do not announce it to the user. Do not
write "Step 0 done". Just internalize what the user already said before
proceeding to Step 1.
---
## Decision-making principle — context first, then ask
Before asking the user **any** question, attempt to derive the answer from
the context already gathered: project summary, module dependencies, entity
details, existing files in the package, prior turns of this conversation,
and the user's original prompt. Only ask when the context yields **no
clear default** or when the choice is genuinely user-specific (e.g. which
entity, which DTO).
Hierarchy of decisions:
1. **Context is unambiguous → decide silently, do NOT ask.**
Examples: language and module from `get_project_summary`; MapStruct
presence from `list_module_dependencies`; mapper package from the
DTO's package; className from `{Entity}Mapper`; componentModel from
Spring presence; mapperType when the user said "MapStruct" or
"Custom" outright.
2. **Context gives a strong signal → state the decision + alternatives in one line, let the user override or stay silent.**
Format:
```
Will create `OrderMapper` (MapStruct, componentModel=spring, in the same package as `OrderDto`).
Alternatives: Custom mapper. OK?
```
The user can answer "ok" / "yes" / silence → accept; or name an
alternative → switch.
3. **Context yields no clear default → ask with `AskUserQuestion` (preferred) or its analogue, with the recommended option first and `(Recommended)` appended.** Fall back to plain text if no interactive tool is available.
4. **Context is fully empty for a critical input → ask plainly.**
This applies to: which entity, which DTO (when neither was mentioned),
the user's intent itself.
### How to ask — prefer `AskUserQuestion`
When a question must be asked, prefer the **`AskUserQuestion`** tool (or
its analogue) over writing a numbered list in the response body. Fall back
to plain text only if no interactive choice tool is available.
Rules for `AskUserQuestion` calls in this skill:
- Each call may contain up to **4 questions** that are independent of each
other (the tool will render them together). Use this to batch related
decisions in one round-trip.
- Each question has **2–4 options**. The tool auto-adds an "Other" choice
for free-form input — never include it manually.
- Mark the recommended option by putting it **first** with `(Recommended)`
appended to the label.
- `header` is a 12-char chip label (e.g. "Mapper", "Methods", "Package").
- Each option has a `description` explaining what the choice means.
When `AskUserQuestion` is **not** the right tool:
- Free-form input where there is no enumerable set of options
(e.g. arbitrary class name) — ask in plain text.
- The "single confirmation line" form from principle 2 — that is a plain
yes/no, not an enumerated choice.
The screen-driven question lists in Steps 2–3 below are a **fallback** for
case 4. They are NOT a script to execute top-to-bottom. If a question's
answer is already determined by principles 1–3, **skip the question**.
---
## Step 1 -- Gather minimal project context (automatic, no questions)
Call only the MCP tools whose result is **actually consumed** by a later
step. Do not pre-fetch "in case we need it" — every variable here must
have a concrete downstream user.
| Tool | What to extract | Variable | Used for |
|------|----------------|----------|----------|
| `get_project_summary` | language, moduleName, buildFile | `language`, `moduleName`, `buildFile` | language → Step 4 reference selection (Java vs Kotlin); moduleName → multi-module disambiguation; buildFile → Step 5 dependency injection |
| `list_module_dependencies(moduleName)` | artifact IDs | `presentDeps` | Step 5 (is MapStruct already present? do we need to add it?) and componentModel decision below |
That is the entire Step 1. **Do NOT** fetch:
- Spring Boot version — no branching depends on it
- application.properties path — Step 5 writes nothing to properties
- `mainPackage` — Step 4 derives the mapper package from the DTO's
package, not from the project root
- `get_entity_details` / `list_class_members` — these depend on knowing
the entity and DTO, which happens in Step 2. Defer them to Step 2.
- `list_entity_mappers` — needed only at Step 13 of the MapStruct
reference (`uses = {...}` resolution) and only per **association
entity**, which is unknown until `entityDetails` is fetched. Defer to
Step 13, do NOT pre-fetch in Step 1.
If multi-module project (multiple modules in `get_project_summary`):
Ask which module to use. Then re-call `list_module_dependencies` for that
module.
### componentModel — simplified
Determine `componentModel` from `presentDeps`:
- Spring (any `spring-boot-starter*` or `spring-context`) → `SPRING`
- Otherwise → `DEFAULT`
CDI is intentionally not auto-detected. If the user has a CDI project
and wants `componentModel = "cdi"`, they will say so explicitly; the
skill should not branch on it by default.
---
## Step 2 -- Entity and DTO
By Step 0 you should already know entity and DTO if the user mentioned
them. Most common cases:
- **User wrote "create a mapper for Order to OrderDto"** → both known, skip
the questions, go straight to the parallel fetch below.
- **`dto-creator` just delegated** → entity and DTO are passed in by the
delegating skill. Never ask, never re-derive.
- **User wrote "create a mapper for Order"** → entity is `Order`. The DTO
is the most recently created/discussed DTO for that entity in this
conversation, OR — if there are multiple candidates — call
`list_entity_dtos(orderFqn)` and pick the unique one. Only ask if
there are multiple and no other signal.
Ask only when context is genuinely empty. When asking, prefer plain text
(entity/DTO names are free-form input — `AskUserQuestion` is the wrong
tool here):
```
Which entity should I create a mapper for? And which DTO to map to?
```
After both entity FQN and DTO FQN are known, call (in parallel):
| Tool | Variable | Used for |
|------|----------|----------|
| `get_entity_details(entityFqn)` | `entityDetails` | Step 4 — building `@Mapping` annotations, detecting non-owner associations with `mappedBy` for `@AfterMapping` |
| `list_class_members(dtoFqn)` | `dtoFields` | Step 4 — comparing DTO fields against entity fields to decide which `@Mapping(source, target)` lines are needed |
Both calls are deferred to Step 2 because they require Step 2's inputs.
They are NOT part of Step 1.
---
## Step 3 -- Mapper type and variant settings
### Mapper type — context first
Apply the **Decision-making principle**. Decide silently when context is
clear:
| Context signal | Decision |
|---|---|
| User said "MapStruct" / "@Mapper" | MapStruct, no question |
| User said "Custom" / "manually" / "static methods" / "extension function" | Custom, no question |
| MapStruct already in `presentDeps` AND user gave no signal | MapStruct (silent or one-line confirmation per principle 2) |
| MapStruct NOT in `presentDeps` AND project is small/simple | MapStruct is still a fine default — Step 5 will add the dependency. State this in the one-line confirmation: "Will create a MapStruct mapper. Will add dependencies to the build file. Alternative: Custom with no dependencies. OK?" |
| User says "use defaults" | MapStruct |
Only fall back to `AskUserQuestion` when **none** of the rows above
matches. Use it with these options:
| Question | Header | Options (first = recommended) |
|----------|--------|-------------------------------|
| What mapper type for `{Entity}` ↔ `{Dto}`? | Mapper | MapStruct (Recommended): interface with @Mapper/@Mapping / Custom: plain class with static methods |
### Variant settings — defaults are usually correct
For both MapStruct and Custom variants the defaults are almost always
correct:
- `className` = `{EntityName}Mapper`
- `packageName` = same package as the DTO
- `partialUpdate` = no
- `updateWithNull` = no
Do NOT batch-ask these settings unless the user explicitly requested
configuration ("configure methods", "I want partial update") or said something
that contradicts a default.
When the user did ask for configuration, use a single `AskUserQuestion`
call (multiSelect: true) with the relevant subset:
| Question | Header | Options (first = recommended) |
|----------|--------|-------------------------------|
| Which methods to add to the mapper? | Methods | toDto + fromDto (Recommended): basic bidirectional conversion / + partialUpdate: update entity from DTO / + updateWithNull: partialUpdate with null overwrite |
Class name and package are free-form — ask in plain text only when the
user said "I want a different name" or "in a different package".
---
## Step 4 -- Generate code
Determine the reference file based on mapper type and language:
- MapStruct + Java -> read `references/mapstruct-java.md`
- MapStruct + Kotlin -> read `references/mapstruct-kotlin.md`
- Custom + Java -> read `references/custom-java.md`
- Custom + Kotlin -> read `references/custom-kotlin.md`
Read the corresponding reference file and follow its Generation order exactly.
### Building @Mapping annotations
Read `references/mapping-annotations.md` for rules on how to build `@Mapping` annotations.
Compare entity fields from `entityDetails` with DTO fields from `dtoFields`:
1. Match DTO fields to entity fields by name
2. For fields with different names, add `@Mapping(source, target)`
3. For association ID fields (e.g. `customerId` -> `customer.id`), add appropriate mapping
4. For flat fields (e.g. `customerName` -> `customer.name`), add expression or source.target mapping
### Reading skeleton and fragments
1. Read the skeleton file from `examples/_skeletons/{variant}-{language}.md`
2. Apply variable substitutions
3. Write the file
4. For each fragment in the generation order:
- Check if the fragment's condition is met
- Read the fragment from `examples/_fragments/{fragment-name}/{language}.md`
- Apply variable substitutions
- Insert/edit into the created file
### Variable substitution rules
- `{packageName}` -> from Step 1 context or user answer
- `{className}` -> from user answer or default `{EntityName}Mapper`
- `{entityClassFqn}` -> entity FQN from context
- `{dtoClassFqn}` -> DTO FQN from context
- `{entityParamName}` -> decapitalized entity short name
- `{dtoParamName}` -> decapitalized DTO short name
- `{methodName}` -> from naming conventions (see `references/method-naming.md`)
- **NEVER substitute anything not listed in Variables section of the example file**
- **NEVER add imports, methods, or code not in the example**
- **FQN handling (CRITICAL):** examples contain FQNs (e.g. `org.mapstruct.Mapper`,
`org.mapstruct.Mapping`, `org.mapstruct.ReportingPolicy`,
`org.mapstruct.MappingConstants.ComponentModel.SPRING`, entity/DTO FQNs). When
writing the final file, you MUST:
1. Replace every FQN in the body with its **short name**
(e.g. `@org.mapstruct.Mapper(...)` -> `@Mapper(...)`,
`org.mapstruct.ReportingPolicy.IGNORE` -> `ReportingPolicy.IGNORE`,
`{entityClassFqn}` -> entity short name, `{dtoClassFqn}` -> DTO short name).
2. Collect every FQN you shortened and emit a corresponding `import` line
right after the `package` statement, sorted, no duplicates.
3. Classes from the same package as the mapper (entity, DTO if collocated)
must NOT be imported — just use the short name.
4. Types from `java.lang` must NOT be imported.
5. Kotlin: same rules — shorten in the body and add `import` lines at the
top. Kotlin does not need imports for classes in the same package.
6. The IDE will NOT optimize imports for you — the file is saved as-is.
---
## Step 5 -- Add MapStruct dependencies (automatic, MapStruct variant only)
For Custom mapper: skip this step entirely. Custom mappers have no
external dependencies.
For MapStruct: check `presentDeps` and add missing artifacts to the
project's build file.
### Required artifacts
| Artifact ID | Group ID | Scope |
|-------------|----------|-------|
| `mapstruct` | `org.mapstruct` | `implementation` |
| `mapstruct-processor` | `org.mapstruct` | `annotationProcessor` (Java) / `kapt` or `ksp` (Kotlin) |
If neither artifact is missing from `presentDeps`, skip the rest of this
step — nothing to add.
### How to edit the build file
Use the `buildFile` path captured in Step 1 — that is the exact file the
skill must edit. Do NOT guess; do NOT search the project for build files.
Pick the editing strategy by file extension:
- **`build.gradle.kts`** — add `implementation("org.mapstruct:mapstruct:{version}")`
inside the existing `dependencies { … }` block. For the processor:
- Java project → `annotationProcessor("org.mapstruct:mapstruct-processor:{version}")`
- Kotlin project → `kapt("org.mapstruct:mapstruct-processor:{version}")` (apply `kotlin("kapt")` plugin if not present) or `ksp(...)` if KSP is already configured
- **`build.gradle`** (Groovy) — same as above, with single-quoted Groovy syntax
- **`pom.xml`** — add a `<dependency>` entry inside `<dependencies>` with `<scope>` matching the role (`compile` for `mapstruct`, processor configured via `maven-compiler-plugin` `<annotationProcessorPaths>`)
For the version: do NOT hardcode. Read the latest stable MapStruct version
from the project's existing version catalogue (e.g. `gradle/libs.versions.toml`)
if present; otherwise use the version that matches the Spring Boot BOM /
project parent if Maven; otherwise emit a property/variable placeholder
and ask the user to confirm.
Use the `Edit` tool with `{buildFile}` as `file_path`. Make the edit
minimally — insert the new lines into the existing dependencies block,
do not rewrite the file.
### No properties needed
Mapper creation does not write any `application.properties` entries.
Report: "Created mapper {className} in package {packageName}. Type: {mapperType}. Methods: {list of methods}."
---
## Anti-hallucination checklist
Before writing ANY code, verify:
- [ ] The code comes from an examples/ file (cite which one)
- [ ] Only declared variables were substituted
- [ ] No framework API calls were added "from knowledge"
- [ ] Import list matches the example exactly
- [ ] Method signatures match the example exactly
- [ ] No comments or convenience methods were added
- [ ] FQNs from examples are shortened in the body AND corresponding `import` lines were added after `package` (IDE will NOT do this for you)
- [ ] @Mapping annotations match entity-DTO field comparison, not guessed
- [ ] Kotlin: multiple @Mapping wrapped in @Mappings(value = [...])
- [ ] @AfterMapping only added when entity has non-owner associations with mappedBy + sub-DTO
- [ ] toDto uses individual `@Mapping(source, target)` pairs by default — `@InheritInverseConfiguration` only when the user explicitly asked for it
- [ ] Flat collection helper is generated only in `toDto` direction; `toEntity` does NOT try to load entities by id (no repository injection)
- [ ] Flat collection helper name follows `{assocFieldName}To{capitalize(flatDtoFieldName)}` (e.g. `petsToPetIds`, not `petsToId`)