Creates MapStruct or custom mappers between entities and DTOs. Use when implementing conversions for CRUD operations or after DTO creation.
npx claudepluginhub amplicode/spring-skills --plugin spring-toolsThis skill uses the workspace's default tool permissions.
Creates a mapper (MapStruct interface/abstract class or custom converter) for converting between an entity and a DTO.
examples/_fragments/after-mapping/java.mdexamples/_fragments/after-mapping/kotlin.mdexamples/_fragments/aggregate-ref-from/java.mdexamples/_fragments/aggregate-ref-from/kotlin.mdexamples/_fragments/aggregate-ref-to/java.mdexamples/_fragments/aggregate-ref-to/kotlin.mdexamples/_fragments/context-class/java.mdexamples/_fragments/context-class/kotlin.mdexamples/_fragments/custom-to-dto/java.mdexamples/_fragments/custom-to-dto/kotlin.mdexamples/_fragments/custom-to-entity/java.mdexamples/_fragments/custom-to-entity/kotlin.mdexamples/_fragments/custom-update-with-null/java.mdexamples/_fragments/custom-update-with-null/kotlin.mdexamples/_fragments/factory-field/java.mdexamples/_fragments/factory-field/kotlin.mdexamples/_fragments/flat-expression/java.mdexamples/_fragments/flat-expression/kotlin.mdexamples/_fragments/full-update-method/java.mdexamples/_fragments/full-update-method/kotlin.mdSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
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.
| 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:
Batch questions: Group related questions into a single AskUserQuestion call (up to 4 questions):
AskUserQuestion; fall back to plain text only if the tool is unavailableBefore 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.
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:
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.
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.
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.
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.
AskUserQuestionWhen 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:
(Recommended)
appended to the label.header is a 12-char chip label (e.g. "Mapper", "Methods", "Package").description explaining what the choice means.When AskUserQuestion is not the right tool:
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.
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:
mainPackage — Step 4 derives the mapper package from the DTO's
package, not from the project rootget_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.
Determine componentModel from presentDeps:
spring-boot-starter* or spring-context) → SPRINGDEFAULTCDI 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.
By Step 0 you should already know entity and DTO if the user mentioned them. Most common cases:
dto-creator just delegated → entity and DTO are passed in by the
delegating skill. Never ask, never re-derive.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.
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 |
For both MapStruct and Custom variants the defaults are almost always correct:
className = {EntityName}MapperpackageName = same package as the DTOpartialUpdate = noupdateWithNull = noDo 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".
Determine the reference file based on mapper type and language:
references/mapstruct-java.mdreferences/mapstruct-kotlin.mdreferences/custom-java.mdreferences/custom-kotlin.mdRead the corresponding reference file and follow its Generation order exactly.
Read references/mapping-annotations.md for rules on how to build @Mapping annotations.
Compare entity fields from entityDetails with DTO fields from dtoFields:
@Mapping(source, target)customerId -> customer.id), add appropriate mappingcustomerName -> customer.name), add expression or source.target mappingexamples/_skeletons/{variant}-{language}.mdexamples/_fragments/{fragment-name}/{language}.md{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)org.mapstruct.Mapper,
org.mapstruct.Mapping, org.mapstruct.ReportingPolicy,
org.mapstruct.MappingConstants.ComponentModel.SPRING, entity/DTO FQNs). When
writing the final file, you MUST:
@org.mapstruct.Mapper(...) -> @Mapper(...),
org.mapstruct.ReportingPolicy.IGNORE -> ReportingPolicy.IGNORE,
{entityClassFqn} -> entity short name, {dtoClassFqn} -> DTO short name).import line
right after the package statement, sorted, no duplicates.java.lang must NOT be imported.import lines at the
top. Kotlin does not need imports for classes in the same package.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.
| 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.
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:
annotationProcessor("org.mapstruct:mapstruct-processor:{version}")kapt("org.mapstruct:mapstruct-processor:{version}") (apply kotlin("kapt") plugin if not present) or ksp(...) if KSP is already configuredbuild.gradle (Groovy) — same as above, with single-quoted Groovy syntaxpom.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.
Mapper creation does not write any application.properties entries.
Report: "Created mapper {className} in package {packageName}. Type: {mapperType}. Methods: {list of methods}."
Before writing ANY code, verify:
import lines were added after package (IDE will NOT do this for you)@Mapping(source, target) pairs by default — @InheritInverseConfiguration only when the user explicitly asked for ittoDto direction; toEntity does NOT try to load entities by id (no repository injection){assocFieldName}To{capitalize(flatDtoFieldName)} (e.g. petsToPetIds, not petsToId)