mapper-creator

>

Skill file

Preview skill file
---
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: Amplicode MCP

This skill is part of the **Spring Agent Toolkit** and is designed to work with the **Amplicode MCP server** (provided by the Amplicode IntelliJ plugin). Before doing anything else, check your tool list for any Amplicode 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`)

Source

Creator's repository · amplicode/spring-skills

View on GitHub

Security

Security checks in progress
Results will appear here once audits complete
What this skill can do
Reads your filesConnects to the internetRuns code on your machine
Checked by 3 independent security firms
Does it try to trick the AI?Not yet checkedPending · Gen Agent Trust Hub
Does it sneak in hidden code?Not yet checkedPending · Socket
Does it have known bugs?Not yet checkedPending · Snyk