
Dto Creator
- 210 installs
- 105 repo stars
- Updated July 27, 2026
- amplicode/spring-skills
Generate Java DTO boilerplate—constructors, equals/hashCode—with Hibernate-aware patterns while mapping entity fields in a Spring project.
About
dto-creator is an agent skill for Spring and Java backends that scaffolds Data Transfer Object class members from selected entity attributes. Solo builders and small teams use it when they need consistent constructor coverage without hand-copying field lists, especially when mutability rules differ between API response DTOs and internal transfer objects. The skill specifies immutable versus mutable behavior explicitly: immutable DTOs get one all-args constructor, while mutable DTOs must include both no-args and all-args forms even if the user only asked for one. Formatting rules keep parameter lists on a single line and preserve detected project indent units. It also points to a Hibernate-oriented equals and hashCode variant for persistence-aware equality instead of naive field comparison. Install it when you are in active build work and want repeatable DTO structure rather than ad-hoc codegen chat.
- Emits all-args constructors with project-aligned indentation from entity attribute types and names
- When isMutable=true, always generates both no-args and all-args constructors (hard rule in skill defaults)
- Immutable path (isMutable=false) emits a single all-args constructor only
- Documents equals()/hashCode() Hibernate proxy variant as a separate insert pattern after getters/setters
- Constructor parameters stay on one line with a blank line between no-args and all-args in the mutable case
Dto Creator by the numbers
- 210 all-time installs (skills.sh)
- Ranked #23 of 89 Java & JVM skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/amplicode/spring-skills --skill dto-creatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 210 |
|---|---|
| repo stars | ★ 105 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | amplicode/spring-skills ↗ |
What it does
Generate Java DTO boilerplate—constructors, equals/hashCode—with Hibernate-aware patterns while mapping entity fields in a Spring project.
Files
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-installis 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."
---
DTO Creator
Creates a DTO class (Java class, Java record, Java+Lombok, or Kotlin data class) for an entity with selected attributes, constructors, getters/setters, equals/hashCode, toString, and optional features.
---
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–7 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.
Step 7 (mapper) is automatic when conversion is needed. If from context it is clear that the DTO will be used in code that converts entities to/from DTOs (controller, service, endpoint replacement, etc.), the skill MUST delegate tomapper-creator— never write manual mapping code inline. Themapper-creatorskill decides the implementation (MapStruct, Custom, adding dependencies) — this skill just delegates.
---
Defaults
| Option | Default | Always ask? | Notes |
|---|---|---|---|
| entity | -- | YES | main branching: which entity to create DTO for |
| attributes | all entity fields | YES | which fields to include |
| className | {EntityName}Dto | NO | suggest, confirm only |
| language | from get_project_summary | NO | auto-detected |
| variant | auto from language + deps | YES | Java class / Java record / Java+Lombok / Kotlin |
| mutable | false | NO | skip unless user wants customization |
| allArgsConstructor | true | NO | Java plain only |
| equalsHashCode | true | NO | Java plain only |
| toString | true | NO | Java plain only |
| fluentSetters | false | NO | only when mutable=true, Java only |
| jsonIgnoreUnknownProperties | false | NO | Java only, when Jackson on classpath |
| serializableType | NoSerializable | NO | rarely needed |
| packageName | same package as entity | NO | auto-detected |
| subDtoType (per ToOne association) | FLAT with id-only sub | YES | Four options available: New Class / New Nested Class / Existing Class / Flat. "Only ID" does NOT exist as a separate option — it is Flat with only the sub-entity id checked. See references/sub-dto.md. |
| subDtoType (per ToMany / collection) | NEW_NESTED_CLASS | YES | same 4 options as ToOne. Flat IS supported for collections and produces composite plural fields like Set<Integer> specialtyIds — see references/sub-dto.md. |
| fieldNameOverride (per field) | none | NO | per-field rename |
| extraValidations (per field) | none | NO | user-added jakarta validators on top of inherited ones |
| removedValidations (per field) | none | NO | constraints inherited from entity that the user wants dropped |
| indent | from .editorconfig (fallback 4-space) | NO | see § Indentation below |
Smart defaults: If user says "use defaults", "all defaults", "default settings", or similar -- skip ALL questions where "Always ask?" = NO. Only ask mandatory questions.
Smart answer recognition: When user provides a value instead of choosing from a numbered list, accept it directly. Examples:
- Question "Which entity?" -> user answers "Order" -> this IS the entity, don't re-ask
- Question "Variant?" -> user answers "record" -> this IS Java record, don't show options
- If user provides multiple answers in one message -> accept all, skip answered questions
- NEVER ask a question that the user already answered (even implicitly)
Batch questions: Group closely related questions into a single AskUserQuestion call (up to 4 questions per call) when they:
- Belong to the same logical section (e.g. both are Java method generation settings)
- Don't depend on each other's answers
- Have obvious defaults that the user can skip
Rules:
- Maximum 3-4 questions per
AskUserQuestioncall - Mark the recommended option with
(Recommended)and place it first - Never batch questions from DIFFERENT decision branches
- The primary branching question (entity selection, variant) is always asked ALONE
- Prefer
AskUserQuestionfor choices; fall back to plain text lists only if the tool is unavailable
---
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 fields).
Hierarchy of decisions:
1. Context is unambiguous → decide silently, do NOT ask. Examples: language and JDK from get_project_summary; Lombok / Jackson / Hibernate Validator from list_module_dependencies; package from the entity FQN; className from {Entity}Dto; back-reference @ManyToOne filtering; auto-selection of sub-entity scalars; record vs. plain class when JDK ≥ 16 and no Lombok and the user did not request otherwise. Exception: subDtoType is NEVER decided silently — it always requires at minimum principle 2 (one-line confirmation), unless the USER explicitly stated the shape in their own message (not in ARGUMENTS).
2. Context gives a strong signal → state the decision + alternatives in one line, let the user override or stay silent. Format:
Will create `ScheduleTemplateDto` as a Java record with nested `SlotDto` (record).
Alternatives: plain class, Lombok, separate file for SlotDto. OK?The user can answer "ok" / "yes" / silence → accept; or name an alternative → switch. This is not the same as the numbered question format — it is a single confirmation line.
3. Context yields no clear default → ask with `AskUserQuestion` (preferred) or its analogue, with the recommended option first. When AskUserQuestion is available, use it with preview fields so the user sees the concrete code shape for each option. Mark the recommended option with (Recommended) in its label and place it first. If no interactive choice tool is available, fall back to a plain text list. Never ask iteratively ("which variant?" → user picks → "which fields?" → …) when one batched call would do.
4. Context is fully empty for a critical input → ask plainly. This applies to: which entity, which fields (when not "all"), 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.
- Use
multiSelect: truefor "which fields to include" or "which
validators to add" — anything where multiple answers are valid.
headeris a 12-char chip label (e.g. "Variant", "Sub-DTO", "Fields").- Each option has a
descriptionexplaining what the choice means or its
consequence (one short sentence).
When AskUserQuestion is not the right tool:
- Free-form input where there is no enumerable set of options
(e.g. arbitrary class name, arbitrary field rename) — ask in plain text.
- The "single confirmation line" form from principle 2 — that is a plain
question with an obvious yes/no, not an enumerated choice.
The screen-driven question lists in Steps 2–5 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. Plugin-wizard fidelity is not a goal — generation correctness is. The plugin's UI exists because a screen can show many controls at once; a chat cannot, so the skill must compensate by deciding more on its own.
---
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 (Vet, Owner, ScheduleTemplate); "for X"; an open file in the IDE; a file path; a recently discussed entity in this conversation |
| purpose | "for REST", "for controller", "for API", "for mapping", "projection", "for service" — drives field selection and the mapper question |
| fields | "all fields", "only id and name", "without password", "with associations", "flat" |
| variant | "record", "plain class", "Lombok", "data class" — also implied by language: Kotlin → data class, no question |
| mapper | "and mapper", "with mapper", "only DTO", "no mapper" |
| className | "name it OwnerSummaryDto", "class Foo" |
| package | "in package …", "next to controller" |
| smart defaults | "use defaults", "all defaults", "default settings", "as usual" |
| sub-DTO shape | "nested", "separate class", "only id", "flat" |
| prior project facts | language, JDK, dependencies — already known if discussed earlier in this conversation; do not re-fetch |
For every input that is explicitly or strongly implicitly answered: mark it as decided and skip the corresponding question in Steps 2–7. Do NOT ask "what entity?" if the user wrote "create DTO for Vet" — Vet is the answer. Do NOT ask "Java record or class?" if the user wrote "make record DtoX" — record is the answer.
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.
CRITICAL: ARGUMENTS ≠ user intent. The ARGUMENTS block at the bottom of this prompt is written by the invoking assistant, NOT by the user. It may contain the assistant's own analysis, assumptions, or field-level details that the user never stated. When determining what the user "already said", look ONLY at the actual user messages in the conversation history — never treat ARGUMENTS as a substitute for user input. In particular, do NOT skip asking about sub-DTO shape just because ARGUMENTS describes one.
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.
---
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 | Variable | Used for |
|---|---|---|
get_project_summary | language, jdkVersion, moduleName | language → variant selection (Step 4) and reference file (java vs kotlin); jdkVersion → canUseRecords = jdkVersion >= 16; moduleName → multi-module disambiguation + parameter for list_module_dependencies |
list_module_dependencies(moduleName) | presentDeps | derived flags below — Lombok/Jackson/Validation feature gates |
That is the entire Step 1. Do NOT fetch:
springBootVersion/bootMajor— no branching depends on itbuildFile— DTO generation does not edit the build file (no
dependencies to add); a DTO needs only language constructs
mainPackage— the DTO's package is derived from the entity's FQN,
not from the project root
list_application_properties_files/propsFile— DTO writes nothing
to application.properties
list_all_domain_entities— needed only if the user did not name an
entity in their prompt. Defer to Step 2 as a lazy fallback.
get_entity_details/list_entity_dtos— depend on knowing the
entity, which happens in Step 2. Defer to Step 2.
Derived variables (all from presentDeps and get_project_summary):
hasLombok=presentDepscontainslombokhasJackson=presentDepscontainsjackson-databindorjackson-corehasValidation=presentDepscontainshibernate-validatororjakarta.validation-apiorspring-boot-starter-validationcanUseRecords=language == JAVAANDjdkVersion >= 16
If multi-module project (multiple modules in get_project_summary): Ask which module to use. Then re-call list_module_dependencies for that module.
---
Step 2 -- Entity selection
By Step 0 you should already know the entity if the user mentioned it. Most common case: the user wrote "create DTO for Vet" → entity is Vet, skip the question, go straight to the parallel fetch below.
Lazy fallback — only when entity is unknown: call list_all_domain_entities(moduleName) → entities, then ask via AskUserQuestion (options = entity names from the list, max 4; if more than 4 entities, use the 4 most likely candidates based on context and add a note that the user can type a different name via "Other").
This is the only place list_all_domain_entities should be called. If the user named the entity in their prompt, do NOT call it.
After the entity FQN is known, call (in parallel):
| Tool | Variable | Used for |
|---|---|---|
get_entity_details(entityFqn) | entityDetails | Step 3 attribute selection, association analysis, validation inheritance, generation order in every variant reference |
list_entity_dtos(entityFqn) for the parent entity is NOT called here — it has no downstream consumer. Sub-entity calls (list_entity_dtos(subEntityFqn)) are still needed for EXIST_CLASS detection in references/sub-dto.md, but they are lazy and per-association, fired only when the user is offered the EXIST_CLASS option. See references/sub-dto.md for the exact place.
DTO name collision is handled by a different lazy call — list_existing_classes(package) — fired right before generation in Step 6 (see anti-hallucination checklist). It is not part of Step 2.
---
Step 3 -- Attribute selection
Default: include every scalar attribute and every association (with the sub-DTO defaults from references/sub-dto.md). Ask only when context signals that the user wants something narrower.
Decide from context (preferred over asking)
Use the purpose captured in Step 0 to pick a sensible default set:
| Purpose signal | Default field set |
|---|---|
| "for REST", "for controller", "for API" | all scalars + all associations expanded (NEW_NESTED_CLASS for ToMany, FLAT id for ToOne) — the user wants the response shape, including related data |
| "projection", "summary", "list view", "for list" | scalars only, ToOne associations as Flat id, ToMany excluded |
| "for mapping", "for service", "DTO for storage" | all scalars + all associations expanded |
| "only id and name" / explicit field list | exactly what the user named, nothing else |
| no purpose signal | all scalars + all associations expanded (richest reasonable default) |
If the chosen default matches the user's apparent intent, do not ask. Just generate. State the choice in the one-line confirmation form (principle 2) at most.
When to ask
Ask only if:
- The user explicitly said "choose fields" / "ask about fields" / "fine-tune
settings", OR
- The entity has many fields and the purpose signal is ambiguous AND the
user did not say "use defaults".
When asking, use AskUserQuestion with multiSelect: true:
| Question | Header | Options (first = recommended) |
|---|---|---|
Which fields to include in DTO {Entity}Dto? | Fields | All fields + associations (Recommended) / Scalars only / Only id and name / Specify manually |
For each association included, apply the sub-DTO defaults from references/sub-dto.md. Do NOT ask per-association unless the user explicitly requested fine-grained control.
---
Step 4 -- Variant selection
Apply the Decision-making principle above. The variant is almost always derivable from context — explicit asking should be the exception, not the default.
If language=KOTLIN: route to references/kotlin.md. No question.
If language=JAVA: decide as follows.
| Context | Action | Variant |
|---|---|---|
hasLombok = true AND user prompt explicitly mentions Lombok | decide silently | references/java-lombok.md |
canUseRecords = true AND hasLombok = false AND user did not ask for mutability/setters/Lombok | decide silently, mention in the one-line confirmation from principle 2 | references/java-record.md |
canUseRecords = false AND hasLombok = false | decide silently | references/java-plain.md |
hasLombok = true AND canUseRecords = true AND user gave no signal | use the one-line confirmation form (principle 2): "Will create as Java record (JDK ≥ 16, no Lombok). Alternatives: plain class, Lombok. OK?" | depends on answer |
| User explicitly said "plain class" / "mutable" / "with setters" | decide silently | references/java-plain.md |
| User explicitly said "record" | decide silently | references/java-record.md |
| User explicitly said "Lombok" | decide silently (only if hasLombok = true; otherwise warn and fall back) | references/java-lombok.md |
Only fall back to the full numbered question when none of the rows above matches AND the user has not said "use defaults". Even then, prefer the "all variants with default marked" format from principle 3 over an iterative question.
Map answer to variant:
- plain →
references/java-plain.md - record →
references/java-record.md - Lombok →
references/java-lombok.md
---
Step 5 -- Variant-specific questions
Follow the variant-specific questions from the selected reference file. Only ask if user did NOT say "all defaults".
---
Step 6 -- Generate code
1. Determine target path: src/main/{java|kotlin}/{packagePath}/{className}.{java|kt}
2. Follow the Generation Order from the selected reference file.
3. For each step:
If skeleton (new file):
- Read the skeleton
.mdfromexamples/_skeletons/ - Apply variable substitutions
- Use Write tool to create the file
If fragment:
- Read the fragment
.mdfromexamples/_fragments/ - Read the Insert Point to know WHERE to insert
- Use Edit tool to insert code at the specified point
- Apply variable substitutions
4. Variable substitution rules:
{packageName}-> from entity package or user choice{className}-> from user or default{EntityName}Dto- Field-level variables -> from entity details
- NEVER substitute anything not listed in Variables
- NEVER add imports, methods, or code not in the example
- FQN handling (CRITICAL): examples contain FQNs (e.g.
java.util.Objects,
java.util.List, jakarta.validation.constraints.NotNull). When writing the final file, you MUST: 1. Replace every FQN in the body of the class with its short name (e.g. java.util.Objects.hash(...) -> Objects.hash(...), java.util.List<Integer> -> List<Integer>, @jakarta.validation.constraints.NotNull -> @NotNull). 2. Collect every FQN you shortened and emit a corresponding import line right after the package statement, sorted, no duplicates. 3. Types from java.lang (String, Integer, Object, ...) must NOT be imported and must appear as short names. 4. Classes from the same package as the DTO must NOT be imported. 5. Javadoc `{@link ...}` references — UNIFORM: Generate short name + import in {@link …} for every shape: top-level Java class, top-level Java record, nested static class, nested record, and separate-file sub-DTO (NEW_CLASS). There is no asymmetry. Always shorten the entity reference and always add the corresponding import line (unless the entity is in the same package). 6. Group imports in two blocks separated by ONE blank line:
- Block 1 — all third-party / project imports together:
jakarta.*, com.fasterxml.*, org.springframework.*, org.hibernate.*, project packages, etc. (alphabetical inside the block).
- (blank line)
- Block 2 —
java.*andjavax.*(alphabetical).
Do NOT split block 1 into per-package sub-blocks. The final file must contain short names in the body (including every Javadoc {@link …}) and a clean, grouped import block at the top.
5. For sub-DTOs (subDtoType=NEW_CLASS): create a separate file by repeating Steps 6.1-6.4 recursively for the sub-entity.
6. For nested classes (subDtoType=NEW_NESTED_CLASS): add inner class to the parent DTO file, then fill it following the same fragment rules.
7. Nested in a record parent — MANDATORY: when the parent DTO is a Java record, every NEW_NESTED_CLASS association MUST be emitted as a nested public record inside the parent record's body. The skill MUST NOT silently fall back to NEW_CLASS (separate file) just because the record-form fragment looks shorter. The full procedure is in references/java-record.md Step 6 and examples/_fragments/nested-class/java/nested-class.md ("Java record" variant). If those instructions seem ambiguous to you, that is a bug in the skill — fix the docs, do NOT work around it by changing the subDtoType.
---
Step 7 -- Mapper (automatic when conversion is needed)
Decide whether a mapper is needed based on context, then act:
| Signal | Action |
|---|---|
| User said "only DTO" / "no mapper" / "without mapper" | Skip Step 7 entirely. Do NOT mention the mapper. |
| User explicitly asked for a mapper ("and mapper", "with mapper", "create mapper too") | Delegate to mapper-creator immediately. |
| From context it is clear that entity↔DTO conversion will happen (user asked to replace entity with DTO in a controller/service/endpoint, user asked to convert/map/transform, DTO is for REST API, etc.) | Delegate to mapper-creator immediately. The conversion is inevitable — creating the DTO without a mapper would force manual inline mapping code, which is never acceptable. |
| Context is silent — no signal about how the DTO will be used | Skip Step 7. Do not mention the mapper. |
CRITICAL: Never write manual mapping code (inline toDto/fromDto methods in controllers, services, or anywhere else). If conversion is needed, always delegate to mapper-creator. That skill decides the implementation strategy (MapStruct, Custom mapper, adding dependencies) — this skill just delegates.
Never ask AskUserQuestion for the mapper. If delegating, invoke the mapper-creator skill with the DTO and entity information directly — do not ask the user to confirm the delegation.
---
Indentation
The skill MUST detect the project's indentation style — never hardcode tabs or spaces. Detection order:
1. `.editorconfig` at the project root (or any parent of the target file's directory). For Java files, look up the [*.java] or [*] section and read indent_style (tab or space) and indent_size / tab_width. 2. Sample existing Java files in the same package (or the nearest ancestor package that contains Java files). Detect whether the leading whitespace on indented lines uses \t or spaces, and how many. 3. Default to 4-space if neither source is conclusive. (Tabs are also acceptable as a default if the developer explicitly prefers them, but the skill must never silently assume one over the other.)
Whatever style is chosen, apply it uniformly to every line of every generated fragment (fields, constructors, getters/setters, equals/hashCode, toString, nested classes/records). Never mix tabs and spaces inside the same file.
Per-field options
The skill must support the following per-field controls:
- Field rename (
fieldNameOverride): the DTO field name can differ from
the entity attribute name. Mapper generation still maps it from the original attribute.
- Add validations (
extraValidations): the user can add jakarta /
hibernate-validator constraints on top of the ones inherited from the entity. The list of allowed constraints depends on the field type — see references/validation.md.
- Remove inherited validations (
removedValidations): the user can drop
any constraint that came from the entity field.
- Edit annotation parameters (
message,min,max,regexp, …): all
parameters of every constraint are editable.
These options never appear unless the user explicitly asks for "fine-tune field settings", "per-field validation" or similar. By default the skill just inherits everything from the entity.
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
importlines were added afterpackage - [ ] Every Javadoc shape — top-level class, top-level record, nested static class, nested record, separate-file sub-DTO — uses short name
{@link Pet}AND adds a matchingimport(unless the entity is in the same package). Uniform rule, no asymmetry. - [ ] If a sub-DTO name (separate file or nested) would collide with an existing class in the target package, auto-suffix with a number (e.g.
PetDto1). The skill must do the same — calllist_existing_classes(package)before generating, and append1,2, … until the name is free. - [ ] When generating a Java record, the inner class for any
NEW_NESTED_CLASSassociation is also a record (not a static class). All component validators are inlined onto the record component parameters (e.g.@NotBlank String firstName), not on separate field declarations. - [ ] When
isJavaRecord = true, the skill MUST NOT emitequals(),hashCode(),toString(), mutable setters, or fluent setters — records auto-generate these and these options should not be available for record DTOs. - [ ] Class-level Javadoc is multi-line (
/**\n * DTO for {@link …}\n */), never collapsed to one line. - [ ] Getters and setters are multi-line (signature line, indented body, closing brace), never one-liners.
- [ ] In
mutable=true && fluentSetters=falsemode, getters and setters are interleaved (getX, setX, getY, setY, …), not grouped. - [ ] In
mutable=truemode, both the no-args constructor and the all-args constructor are emitted. - [ ] The skill does NOT offer "Only ID" as a separate option — this option does not exist (neither for ToOne nor for ToMany). The "association id only" effect is produced by Flat with only the sub-entity `id` checked, and the skill must implement it that way.
- [ ] For collection associations (
List<X>,Set<X>), Flat is offered and produces composite plural fields (Set<Integer> specialtyIds,List<Integer> petIds), provided the skill auto-checks the sub-entity scalars. - [ ] Back-reference
@ManyToOnefields are filtered out of the attribute list (e.g.Pet.owneris not offered when creatingPetDto). - [ ] Imports are grouped with a blank line between
jakarta.*/com.*/org.*/java.*blocks. - [ ] Indentation matches the project's detected style (
.editorconfigfirst, then sampling existing files in the same package, then 4-space default). Never hardcode tabs or spaces. Apply uniformly across every fragment in the file. - [ ] @JsonIgnoreProperties is NOT added for Kotlin (Java-only feature)
- [ ] Validation annotations use
@field:prefix in Kotlin - [ ] Java types are converted to Kotlin types for Kotlin DTOs
All-args constructor (Java)
Insert Point
As constructor(s) in class body, after fields.
Code
// Default: all-args constructor only (when isMutable=false).
// When isMutable=true, BOTH a no-args and an all-args constructor are
// emitted. The skill MUST generate both even if the user did not explicitly
// ask for the no-args one.
// Indentation: Use the project's detected indent unit (see `SKILL.md` § Indentation).Immutable (isMutable=false) -- all-args constructor only:
public {className}({Type1} {field1}, {Type2} {field2}) {
this.{field1} = {field1};
this.{field2} = {field2};
}Mutable (isMutable=true) -- BOTH no-args and all-args constructors:
public {className}() {
}
public {className}({Type1} {field1}, {Type2} {field2}) {
this.{field1} = {field1};
this.{field2} = {field2};
}Formatting rules
- One blank line between the no-args and the all-args constructor (mutable case).
- Constructor parameters all on a single line — no per-parameter wrapping,
even for many fields.
Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
{fieldN} | field names from selected entity attributes | -- |
{TypeN} | field types from selected entity attributes | -- |
equals() and hashCode() with Hibernate proxy support (Java)
Insert Point
As methods in class body, after getters/setters.
When to use
Use this variant INSTEAD of equals-hashcode.md when all of:
- Hibernate is on the classpath (
presentDepscontainshibernate-core). - The user explicitly opted in to proxy-aware equals (variant question
"Hibernate-proxy aware equals?" answered "yes").
If either condition is false, use the plain equals-hashcode.md.
Code
// Default: skip. Use only when conditions above are met.@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof org.hibernate.proxy.HibernateProxy
? ((org.hibernate.proxy.HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass()
: o.getClass();
Class<?> thisEffectiveClass = this instanceof org.hibernate.proxy.HibernateProxy
? ((org.hibernate.proxy.HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass()
: this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
{className} entity = ({className}) o;
return java.util.Objects.equals(this.{field1}, entity.{field1}) &&
java.util.Objects.equals(this.{field2}, entity.{field2});
}
@Override
public int hashCode() {
return this instanceof org.hibernate.proxy.HibernateProxy
? ((org.hibernate.proxy.HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode()
: getClass().hashCode();
}FQN handling
Shorten org.hibernate.proxy.HibernateProxy in the body and add a single import org.hibernate.proxy.HibernateProxy; line.
Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
{fieldN} | field names from selected entity attributes | -- |
equals() and hashCode() (Java)
Insert Point
As methods in class body, after getters/setters.
Code
// Default: generated (isEqualsHashCode=true by default).
// Use the plain variant unless Hibernate is on the classpath AND the user
// asked for proxy-aware equals -- then use the Hibernate-proxy variant from
// equals-hashcode-hibernate.md.
// Indentation: Use the project's detected indent unit (see `SKILL.md` § Indentation).@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
{className} entity = ({className}) o;
return java.util.Objects.equals(this.{field1}, entity.{field1}) &&
java.util.Objects.equals(this.{field2}, entity.{field2});
}
@Override
public int hashCode() {
return java.util.Objects.hash({field1}, {field2});
}When no fields:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return true;
}
@Override
public int hashCode() {
return java.util.Objects.hash();
}Formatting rules
- Cast variable name is always
entity. - The
&&chain hangs on the right side of the previous line; each
continuation line is indented one extra tab beyond return.
- Methods are separated by a single blank line.
Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
{fieldN} | field names from selected entity attributes | -- |
Java fields (Lombok mode)
Insert Point
Inside the class body, as field declarations.
Code
// Default: no access/final modifiers (Lombok handles them via @Value/@Data/@Getter)For each selected attribute, generate a field:
Default (Lombok manages access and mutability):
{validationAnnotations}{Type} {fieldName};Validation annotations -- each on separate line before the field:
@jakarta.validation.constraints.NotNull
@jakarta.validation.constraints.Size(min = 0, max = 255)
java.lang.String name;For collection types, wrap: java.util.List<{InnerType}>, java.util.Set<{InnerType}>.
For sub-DTO attributes (subDtoType != FLAT), use the DTO FQN as the type instead of entity FQN.
Variables
| Variable | Source | Default |
|---|---|---|
{fieldName} | entity attribute name | -- |
{Type} | entity attribute type FQN (or DTO FQN for sub-DTOs), wrapped in collection if applicable | -- |
{validationAnnotations} | from entity validation annotations, each @fqn on separate line | empty |
Java record components
Insert Point
Replace the empty record header () in the record declaration.
Code
// Default: record components from all selected attributesGenerate record components joined by , :
public record {className}({validationAnnotations}{Type1} {field1}, {validationAnnotations}{Type2} {field2}) {
}With validation annotations (inline before type):
public record {className}(@jakarta.validation.constraints.NotNull java.lang.String name, java.lang.Long id) {
}Variables
| Variable | Source | Default |
|---|---|---|
{className} | user choice | -- |
{fieldN} | entity attribute name | -- |
{TypeN} | entity attribute type FQN | -- |
{validationAnnotations} | from entity validation annotations, inline before type | empty |
Java fields (non-record, non-Lombok)
Insert Point
Inside the class body, as field declarations.
Code
// Default: immutable fields (private final).
// Indentation: Use the project's detected indent unit (see `SKILL.md` § Indentation).
// Each field line is indented with one unit. Validation annotations sit on
// their own lines, also indented with one unit. No blank lines between fields.For each selected attribute, generate a field:
Immutable (isMutable=false, default):
{validationAnnotations}private final {Type} {fieldName};Mutable (isMutable=true):
{validationAnnotations}private {Type} {fieldName};Mutable with entity initializer:
{validationAnnotations}private {Type} {fieldName} = {entityInitializer};Validation annotations -- each on separate line before the field:
@jakarta.validation.constraints.NotNull
@jakarta.validation.constraints.Size(min = 0, max = 255)
private final java.lang.String name;For collection types, wrap: java.util.List<{InnerType}>, java.util.Set<{InnerType}>.
For sub-DTO attributes (subDtoType != FLAT), use the DTO FQN as the type instead of entity FQN.
Variables
| Variable | Source | Default |
|---|---|---|
{fieldName} | entity attribute name | -- |
{Type} | entity attribute type FQN (or DTO FQN for sub-DTOs), wrapped in collection if applicable | -- |
{validationAnnotations} | from entity validation annotations, each @fqn on separate line | empty |
{entityInitializer} | from entity field initializer (only when mutable) | none |
Kotlin constructor parameters
Insert Point
As primary constructor parameters of the data class / class.
Code
// Default: immutable (val), non-nullable, 4-space indented multi-line for top-level data classesFor each selected attribute, generate a constructor parameter. The line is indented 4 spaces inside the constructor parameter list, and parameters are comma-separated with one per line for top-level data classes.
Immutable, non-nullable (default):
val {fieldName}: {Type},Mutable (when "Mutable fields" is selected — swaps every val to var, nothing else changes):
var {fieldName}: {Type},Nullable:
val {fieldName}: {Type}?,With validation annotations — short name + import (NOT FQN), @field: target prefix, on the same line as the field declaration:
@field:NotEmpty val {fieldName}: {Type},Multiple validation annotations on one field — space-separated, all on the same line as the field:
@field:Digits(integer = 10, fraction = 0) @field:NotEmpty val {fieldName}: {Type},The validator parameters are written in the annotation type's declaration order (e.g. @Digits(integer, fraction)), not the order they appeared in the source entity. Empty validator parameter set → bare annotation without parentheses (@field:NotEmpty, never @field:NotEmpty()).
For collection types: preserve the entity's collection type. If the entity field is MutableSet<Pet>, the DTO field is MutableSet<PetDto> (or MutableSet<Int> for Flat-id). Do NOT force List. Set, MutableSet, List, MutableList are all valid depending on the entity declaration.
For sub-DTO attributes:
subDtoType=NEW_NESTED_CLASS: type is the nested class short name (e.g.MutableSet<PetDto>).subDtoType=NEW_CLASS: type is the separate-file DTO short name (with import).subDtoType=EXIST_CLASS: type is the existing DTO short name (with import unless same package).subDtoType=FLATwith onlyidselected: composite name{singularAssociationName}Id(singular) for ToOne,{singularAssociationName}Idsfor ToMany. Type isInt/MutableSet<Int>/MutableList<Int>/Set<Int>/List<Int>matching the entity collection.subDtoType=FLATwith multiple sub-attributes selected: composite per-attribute names liketypeName: String,typeId: Int.
Java-to-Kotlin type conversion: java.lang.String → kotlin.String, java.lang.Long → kotlin.Long, java.lang.Integer → kotlin.Int, java.lang.Boolean → kotlin.Boolean, java.math.BigDecimal stays java.math.BigDecimal, java.time.LocalDate stays java.time.LocalDate.
Single-line vs multi-line form
- Top-level data class with validations or > 3 short scalars: multi-line, 4-space indent, one parameter per line, no trailing comma.
- Nested data class with only short scalars and no validators: single-line, all parameters on the same line as the
data class Foo(...)declaration. See_fragments/nested-class/kotlin/nested-class.md.
Variables
| Variable | Source | Default |
|---|---|---|
{fieldName} | entity attribute name (or composite Flat-id name) | -- |
{Type} | entity attribute Kotlin type (preserving collection type and nullability) | -- |
{validationAnnotations} | from entity validation annotations (or user overrides), each @field:{ShortName} inline on the same line as the field | empty |
Fluent setters (Java)
Insert Point
As methods in class body, after all getters (when fluent setters are chosen, getters are NOT interleaved with setters — they form their own block first, then all fluent setters follow).
Code
// Default: skip (only generated when isMutable=true && isFluentSetters=true).
// Indentation: Use the project's detected indent unit (see `SKILL.md` § Indentation).For each field:
public {className} set{Name}({Type} {fieldName}) {
this.{fieldName} = {fieldName};
return this;
}Formatting rules
- Multi-line, body indented with one unit (see
SKILL.md§ Indentation). - One blank line between consecutive fluent setters.
- Return type is the DTO class name (NOT
void).
Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
{Name} | capitalized field name | -- |
{fieldName} | field name | -- |
{Type} | field type | -- |
Getters (Java)
Insert Point
As methods in class body, after constructors. When `isMutable=true`, getters are NOT emitted as a single block — they are interleaved with setters (`getX, setX, getY, setY, …`). See `setters.md` for the interleaved layout.
Code
// Default: always generated for Java non-record non-Lombok.
// Format: multi-line, body indented.For each field (always uses get prefix, even for boolean):
public {Type} get{Name}() {
return {fieldName};
}Formatting rules
- Multi-line: signature on its own line,
return ...;indented one level,
closing } on its own line. Never collapse to one line.
- One blank line between consecutive getters (and between a getter and the
next setter when interleaved in mutable mode).
- Indentation: Use the project's detected indent unit (see
SKILL.md§ Indentation).
Variables
| Variable | Source | Default |
|---|---|---|
{Name} | capitalized field name | -- |
{fieldName} | field name | -- |
{Type} | field type | -- |
Standard setters (Java)
Insert Point
Interleaved with getters, NOT in a separate block at the end. Emit methods in the order:
getField1
setField1
<blank line>
getField2
setField2
<blank line>
...i.e. for each selected field a get then a set then a blank line, in the same order as the fields. There is no "all getters first, then all setters" section.
Code
// Default: skip (only generated when isMutable=true && isFluentSetters=false).
// Format: multi-line, interleaved with getters.For each field:
public void set{Name}({Type} {fieldName}) {
this.{fieldName} = {fieldName};
}Formatting rules
- Multi-line: signature on its own line, body indented one level, closing
} on its own line. Never collapse to one line.
- No blank line between a getter and its paired setter; one blank line
before the next field's getter.
- Indentation: Use the project's detected indent unit (see
SKILL.md§ Indentation).
Variables
| Variable | Source | Default |
|---|---|---|
{Name} | capitalized field name | -- |
{fieldName} | field name | -- |
{Type} | field type | -- |
Javadoc with entity link (Java)
Insert Point
Immediately above the class/record declaration (top-level) OR immediately above a public static class … / public record … declaration (nested).
Code
// Default: generated for every Java class, every Java record, every Java
// nested class and every Java nested record created for an entity.
// IMPORTANT: format is ALWAYS multi-line (3 lines), never one-liner.Top-level Java class — short name + import:
import {entityFqn};
/**
* DTO for {@link {entityShortName}}
*/
public class {className} {Top-level Java record — short name + import (same form as class):
import {entityFqn};
/**
* DTO for {@link {entityShortName}}
*/
public record {className}(...) {Nested static class OR nested record — short name + import (same):
/**
* DTO for {@link {subEntityShortName}}
*/
public static class {subDtoName} { … }or
/**
* DTO for {@link {subEntityShortName}}
*/
public record {subDtoName}(...) { }FQN handling — UNIFORM
Generate the same rule everywhere: short name + import. This applies to all shapes: top-level Java class, top-level Java record, nested static class, nested record. There is no asymmetry.
| File shape | {@link …} form | import for entity |
|---|---|---|
| Top-level Java class | short name {@link X} | Add import …X; |
| Top-level Java record | short name {@link X} | Add import …X; |
| Nested static class | short name {@link X} | Add import …X; |
| Nested record | short name {@link X} | Add import …X; |
Separate-file sub-DTO (NEW_CLASS, both class and record) | short name {@link X} | Add import …X; |
If the entity is in the same package as the DTO, no import is needed (standard Java import rules), and the short name is used directly.
Variables
| Variable | Source | Default |
|---|---|---|
{entityFqn} | full FQN of the source entity (used in import line only) | -- |
{entityShortName} | short class name of the source entity | -- |
{subEntityShortName} | short class name of the sub-entity (nested / NEW_CLASS) | -- |
{className} | DTO class/record name | -- |
{subDtoName} | nested or separate-file DTO name | -- |
@JsonIgnoreProperties (Java only)
Insert Point
As annotation on class declaration.
Code
// Default: skip (isJsonIgnoreUnknownProperties=false by default)@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class {className} {Note: This annotation is Java-only. It is NOT generated for Kotlin DTOs. If user selects Kotlin + JsonIgnoreProperties, skip silently.
Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
Lombok annotations (Java)
Insert Point
As annotations on class declaration.
Code
// Default: @lombok.Value (when all default options: equalsHashCode=true, allArgsConstructor=true, toString=true, mutable=false)Case 1: @Value (equalsHashCode=true AND allArgsConstructor=true AND toString=true AND mutable=false):
@lombok.Value
public class {className} {Case 2: @Data (equalsHashCode=true AND toString=true, but NOT all conditions for @Value):
@lombok.Data
public class {className} {When additionally allArgsConstructor=true AND mutable=true:
@lombok.Data
@lombok.AllArgsConstructor
@lombok.NoArgsConstructor
public class {className} {Case 3: Individual annotations (when NOT (equalsHashCode=true AND toString=true)):
@lombok.AllArgsConstructor
@lombok.NoArgsConstructor
@lombok.Getter
@lombok.Setter
@lombok.EqualsAndHashCode
@lombok.ToString
public class {className} {Rules for Case 3:
@lombok.AllArgsConstructor-- when allArgsConstructor=true@lombok.NoArgsConstructor-- when allArgsConstructor=true AND mutable=true@lombok.Getter-- always@lombok.Setter-- when mutable=true@lombok.EqualsAndHashCode-- when equalsHashCode=true@lombok.ToString-- when toString=true
Additional (all cases), when mutable=true AND fluentSetters=true:
@lombok.experimental.Accessors(chain = true)Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
Nested/inner class (Java)
Insert Point
Java class parent (non-record): immediately before the closing } of the parent DTO class body, after the last generated member (last getter/setter/equals/hashCode/toString). One blank line above and below the nested declaration.
Java record parent: immediately before the closing } of the parent record. The parent record's body is { } (empty) when no nested classes are generated; for each NEW_NESTED_CLASS association, insert the nested record inside that body. One blank line between the opening { and the first nested record, and one blank line between adjacent nested records.
The skill MUST use the Edit tool with the closing } of the parent as the anchor — never re-write the whole file.
Code
// Default: skip (only when attribute has subDtoType=NEW_NESTED_CLASS)Java class (non-record) — nested static class:
public static class {subDtoName} {
// fields, constructors, getters/setters generated recursively
}The Javadoc above the public static class … line uses the short name + import form (see examples/_fragments/javadoc/java/javadoc.md, "Nested class" variant). The body of the nested class is generated recursively following the same Generation Order as the parent (fields, constructor, getters, equals, toString, …) and inherits the parent's indent style.
Java record — nested record (REQUIRED when parent is a record):
public record {subDtoName}({Type1} {field1}, {Type2} {field2}) {
}When the parent DTO is a public record, the inner class for every NEW_NESTED_CLASS association MUST also be a public record — never a public static class. The skill MUST NOT silently fall back to NEW_CLASS (separate file) just because the record form looks shorter.
How to fill the nested record's component list
For each scalar attribute of the sub-entity (auto-selected per references/sub-dto.md, "Auto-selection of sub-entity attributes"), generate a record component using the same rules as the top-level record body — i.e. examples/_fragments/fields/java-record.md:
- One component per scalar:
{Type} {fieldName} - All inherited validators are inlined onto the component parameter
(e.g. @NotBlank String name, @Pattern(regexp="…") @NotBlank String telephone)
- Multiple validators on the same component are space-separated
- Components are comma-separated, one per line, indented one level deeper
than the public record …( line
- Back-reference associations of the sub-entity are filtered out (same rule
as for top-level — see references/sub-dto.md, "Filtered fields")
- The sub-entity's own associations are NOT expanded recursively in the
nested record (only scalars). If the user wants deep nesting, they must ask for it explicitly.
The nested record does NOT need its own skeleton, @JsonIgnoreProperties, or Serializable — those apply only to the top-level DTO.
The Javadoc above the public record {subDtoName}(…) line uses the short name + import form (same "Nested class" variant of examples/_fragments/javadoc/java/javadoc.md).
Example — ScheduleTemplate with List<Slot> slots
Parent record (after Step 6 of references/java-record.md):
public record ScheduleTemplateDto(Integer id, String name, List<SlotDto> slots) {
/**
* DTO for {@link Slot}
*/
public record SlotDto(Integer id, String expression) {
}
}Note: the parent record's slots component already uses the nested SlotDto short name as the element type — the field type substitution happens in fields/java-record.md, not here.
Variables
| Variable | Source | Default |
|---|---|---|
{subDtoName} | sub-DTO class name from attribute | {SubEntityName}Dto (auto-suffixed on collision) |
{fieldN} | sub-entity scalar attribute names | -- |
{TypeN} | sub-entity scalar attribute types | -- |
Nested data class (Kotlin)
Insert Point
As a nested data class inside the parent DTO body. Indented 4 spaces.
Code
// Default: single-line nested data class with KDoc; multi-line only if children themselves wrap or carry validatorsSingle-line form (preferred when only short scalars, no validators):
/**
* DTO for [{entityFqn}]
*/
data class {subDtoName}(val {field1}: {Type1}, val {field2}: {Type2}, val {field3}: {Type3})Multi-line form (when validators are present, or constructor would be too long):
/**
* DTO for [{entityFqn}]
*/
data class {subDtoName}(
val {field1}: {Type1},
@field:NotEmpty val {field2}: {Type2},
val {field3}: {Type3}
)Notes
- KDoc is always present, also using FQN form
[fully.qualified.EntityName](same rule as the top-level class — no short-name + import shortcut). - The nested class is
data class, notclass— Kotlin always emits data classes for nested DTOs too. - Auto-suffix on collision still applies: if a
PetDtoalready exists somewhere accessible (top-level in the same package, or another nested), usePetDto1,PetDto2, etc. - When an association becomes a nested class, auto-include all scalar children and leave sub-associations out by default.
- Back-reference filtering: follow the same rule as in
SKILL.md(the global anti-hallucination checklist).
Variables
| Variable | Source | Default |
|---|---|---|
{subDtoName} | sub-DTO class name from attribute (auto-suffixed on collision) | -- |
{entityFqn} | sub-entity FQN | -- |
{field*} / {Type*} | child constructor parameters generated recursively via _fragments/fields/kotlin.md | -- |
Serializable (Java)
Insert Point
Modifies class declaration: adds implements java.io.Serializable.
Code
// Default: skip (depends on project config serializableType)SerializableType.Serializable:
public class {className} implements java.io.Serializable {
}SerializableType.SerializableWithVersionUID:
public class {className} implements java.io.Serializable {
private static final long serialVersionUID = 1L;
}Note: the serialVersionUID value is computed from the entity class. Use 1L as default.
Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
Serializable (Kotlin)
Insert Point
Modifies class declaration: adds : java.io.Serializable supertype.
Code
// Default: skip (depends on project config serializableType)SerializableType.Serializable:
data class {className}() : java.io.SerializableSerializableType.SerializableWithVersionUID:
data class {className}() : java.io.Serializable {
companion object {
private const val serialVersionUID = 1L
}
}Note: the serialVersionUID value is computed from the entity class. Use 1L as default.
Variables
| Variable | Source | Default |
|---|---|---|
{className} | DTO class name | -- |
toString() (Java)
Insert Point
As method in class body, after equals/hashCode.
Code
// Default: generated (isToString=true by default).
// Format: separator ", " is appended at the end of each non-last line,
// NOT prepended at the start of the next one.
// Indentation: Use the project's detected indent unit (see `SKILL.md` § Indentation).@Override
public String toString() {
return getClass().getSimpleName() + "(" +
"{field1} = " + {field1} + ", " +
"{field2} = " + {field2} + ", " +
"{fieldLast} = " + {fieldLast} + ")";
}Single field:
@Override
public String toString() {
return getClass().getSimpleName() + "(" +
"{field1} = " + {field1} + ")";
}No fields:
@Override
public String toString() {
return getClass().getSimpleName() + "()";
}Formatting rules
- Every field line except the LAST one ends with
+ ", " +. - The LAST field line ends with
+ ")";. - The opening
"("stays on thereturn ...line. - Continuation lines are indented one extra tab beyond
return.
Variables
| Variable | Source | Default |
|---|---|---|
{fieldN} | field names from selected entity attributes | -- |
{fieldLast} | last selected field name | -- |
Java class skeleton
Code
// Default: plain Java class, no Javadocpackage {packageName};
public class {className} {
}Variables
| Variable | Source | Default |
|---|---|---|
{packageName} | project context | -- |
{className} | user choice | {EntityName}Dto |
Java record skeleton
Code
// Default: plain Java record, no Javadocpackage {packageName};
public record {className}() {
}Variables
| Variable | Source | Default |
|---|---|---|
{packageName} | project context | -- |
{className} | user choice | {EntityName}Dto |
Kotlin plain class skeleton
Code
// Default: Kotlin plain class (non-data), no KDocpackage {packageName}
class {className}Variables
| Variable | Source | Default |
|---|---|---|
{packageName} | project context | -- |
{className} | user choice | {EntityName}Dto |
Kotlin data class skeleton
Code
// Default: Kotlin data class, KDoc with FQN reference, NO Serializable supertypepackage {packageName}
{importsBlock}
/**
* DTO for [{entityFqn}]
*/
data class {className}()Variables
| Variable | Source | Default |
|---|---|---|
{packageName} | project context | -- |
{className} | user choice | {EntityName}Dto (auto-suffixed Dto1, Dto2, … on collision) |
{entityFqn} | source entity FQN | -- |
{importsBlock} | union of all imports actually used by fields/validators/sub-DTOs, blank line after last import | -- |
Notes
- KDoc is always present and uses the fully-qualified entity name in
[…]form, even when the DTO sits in the same package as the entity. Kotlin does NOT shorten + import the entity reference (unlike Java's{@link}rule). - The skeleton above is the empty shape; constructor parameters are added by
_fragments/fields/kotlin.md. - For nested data classes, the same skeleton applies recursively but is usually emitted in single-line form (see
_fragments/nested-class/kotlin/nested-class.md). - Do not emit
: java.io.Serializableby default — leave it off unless the user explicitly opts in via serializableType.
Java + Lombok DTO
Conditions
- language = JAVA
- isJavaRecord = false
- Lombok on classpath AND useLombokDto setting enabled
Generation Order
1. Skeleton: Read examples/_skeletons/java/class.md -- create file 2. Class Javadoc: Read examples/_fragments/javadoc/java/javadoc.md ("Top-level class" variant) -- insert immediately above the public class {className} { line. Always generated. Uses the uniform short name + import rule (see _fragments/javadoc/java/javadoc.md). 3. Lombok annotations: Read examples/_fragments/lombok/lombok.md -- add annotations on class 4. Fields: Read examples/_fragments/fields/java-lombok.md -- add fields to class body (NO private/final modifiers -- Lombok handles them) 5. @JsonIgnoreProperties (when jsonIgnoreUnknownProperties=true AND Jackson on classpath): Read examples/_fragments/json-ignore/java/json-ignore.md -- add annotation on class 6. Serializable (when serializableType != NoSerializable): Read examples/_fragments/serializable/java/serializable.md -- modify class declaration 7. Nested classes (for each attribute with subDtoType=NEW_NESTED_CLASS): Read examples/_fragments/nested-class/java/nested-class.md. Apply examples/_fragments/javadoc/java/javadoc.md ("Nested class" variant) above the inner class — short name + import.
Lombok Field Rules
When Lombok is active, field generation changes:
privatemodifier is OMITTED (Lombok generates access)finalmodifier is OMITTED when @Value is used (Lombok makes fields final)- NO getters, setters, constructors, equals/hashCode, or toString are generated manually -- Lombok handles them
Variant-Specific Questions
Prefer AskUserQuestion for all questions below; fall back to plain text only if the tool is unavailable. Batch independent questions into one AskUserQuestion call (up to 4 questions per call).
Batch 1 — basic settings (3 questions):
| Question | Header | Options (first = default) |
|---|---|---|
| Mutable DTO (with setters)? | Mutable | No (Recommended) / Yes |
| Fluent setters (@Accessors(chain=true))? | Fluent | No (Recommended) / Yes |
| Constructor with all fields? | Constructor | Yes (Recommended) / No |
Note: fluent setters question only if mutable=true.
Batch 2 — standard methods (3 questions):
| Question | Header | Options (first = default) |
|---|---|---|
| equals() and hashCode()? | Equals | Yes (Recommended) / No |
| toString()? | ToString | Yes (Recommended) / No |
| @JsonIgnoreProperties(ignoreUnknown=true)? | JsonIgnore | No (Recommended) / Yes |
Lombok Annotation Selection
Decision tree (from examples/_fragments/lombok/lombok.md):
1. equalsHashCode=true AND allArgsConstructor=true AND toString=true AND mutable=false -> @lombok.Value 2. equalsHashCode=true AND toString=true (but NOT all @Value conditions) -> @lombok.Data (+ @lombok.AllArgsConstructor + @lombok.NoArgsConstructor if allArgsConstructor=true AND mutable=true) 3. Otherwise -> individual annotations: @lombok.Getter, @lombok.Setter (if mutable), @lombok.AllArgsConstructor (if allArgsConstructor), etc. 4. Additionally: mutable=true AND fluentSetters=true -> add @lombok.experimental.Accessors(chain = true)
Java Plain Class DTO
Conditions
- language = JAVA
- isJavaRecord = false
- Lombok NOT on classpath OR useLombokDto = false
Generation Order
1. Skeleton: Read examples/_skeletons/java/class.md -- create file 2. Class Javadoc: Read examples/_fragments/javadoc/java/javadoc.md ("Top-level class" variant) -- insert immediately above the public class {className} { line. Always generated. 3. Fields: Read examples/_fragments/fields/java.md -- add fields to class body 4. Constructors: Read examples/_fragments/constructor/java/all-args.md -- add after fields.
- When
isMutable=falseANDallArgsConstructor=true-> only the all-args constructor. - When
isMutable=true-> BOTH the no-args constructor AND the all-args constructor are emitted, even if the user did not explicitly ask for the no-args one.
5. Getters + setters:
- When
isMutable=false: readexamples/_fragments/getters-setters/java/getters.mdand emit getters in field order, after the constructor(s). - When
isMutable=true AND fluentSetters=false: emit getters and setters interleaved (getX, setX, getY, setY, …) — seeexamples/_fragments/getters-setters/java/setters.mdfor the layout. Do NOT emit all getters first and then all setters. - When
isMutable=true AND fluentSetters=true: emit getters as a single block (not interleaved), then all fluent setters fromexamples/_fragments/fluent-setters/java/fluent-setters.md.
6. equals/hashCode (when equalsHashCode=true):
- If
useHibernateProxyEquals=true(Hibernate on classpath AND user opted in):
Read examples/_fragments/equals-hashcode/java/equals-hashcode-hibernate.md.
- Otherwise: Read
examples/_fragments/equals-hashcode/java/equals-hashcode.md.
Add after getters/setters. 7. toString (when toString=true): Read examples/_fragments/tostring/java/tostring.md -- add after equals/hashCode 8. @JsonIgnoreProperties (when jsonIgnoreUnknownProperties=true AND Jackson on classpath): Read examples/_fragments/json-ignore/java/json-ignore.md -- add annotation on class 9. Serializable (when serializableType != NoSerializable): Read examples/_fragments/serializable/java/serializable.md -- modify class declaration 10. Nested classes (for each attribute with subDtoType=NEW_NESTED_CLASS):
- Read
examples/_fragments/nested-class/java/nested-class.md-- add inner class. - Apply
examples/_fragments/javadoc/java/javadoc.md("Nested class" variant)
above the public static class ... line — short name + import.
- Recursively generate the inner class contents using the same Generation
Order (Javadoc, fields, constructor, getters, equals, toString, ...).
Variant-Specific Questions
Prefer AskUserQuestion for all questions below; fall back to plain text only if the tool is unavailable. Batch independent questions into one AskUserQuestion call (up to 4 questions per call).
Batch 1 — basic settings (3 questions):
| Question | Header | Options (first = default) |
|---|---|---|
| Mutable DTO (with setters)? | Mutable | No (Recommended) / Yes |
| Fluent setters (return this)? | Fluent | No (Recommended) / Yes |
| Constructor with all fields? | Constructor | Yes (Recommended) / No |
Note: fluent setters question only if mutable=true.
Batch 2 — standard methods (3 questions):
| Question | Header | Options (first = default) |
|---|---|---|
| equals() and hashCode()? | Equals | Yes (Recommended) / No |
| toString()? | ToString | Yes (Recommended) / No |
| @JsonIgnoreProperties(ignoreUnknown=true)? | JsonIgnore | No (Recommended) / Yes |
Batch 3 — only if presentDeps contains hibernate-core AND equalsHashCode=true (1 question):
| Question | Header | Options (first = default) |
|---|---|---|
| equals/hashCode with Hibernate proxy support? | Hibernate | No (Recommended) / Yes |
(Sets useHibernateProxyEquals. Skip entirely when Hibernate is not on the classpath.)
Validation Rules
- At least one of allArgsConstructor or mutable must be true (otherwise fields cannot be initialized)
- fluentSetters only available when mutable=true
useHibernateProxyEquals=truerequiresequalsHashCode=trueAND
hibernate-core in presentDeps
Java Record DTO
Conditions
- language = JAVA
- isJavaRecord = true
- JDK >= 16
Generation Order
1. Skeleton: Read examples/_skeletons/java/record.md -- create file 2. Class Javadoc: Read examples/_fragments/javadoc/java/javadoc.md ("Top-level Java record" variant) -- insert immediately above the public record {className}(...) { line. Always generated. Uses short name + import (same as the top-level class form) — there is no special FQN form for records. 3. Record components: Read examples/_fragments/fields/java-record.md -- replace empty () with components. Validators are inlined onto each component parameter, not on separate field declarations: @NotBlank String firstName, @Pattern(regexp="…") @NotBlank String telephone, …. Multiple validators on the same component are space-separated. 4. @JsonIgnoreProperties (when jsonIgnoreUnknownProperties=true AND Jackson on classpath): Read examples/_fragments/json-ignore/java/json-ignore.md -- add annotation on record 5. Serializable (when serializableType != NoSerializable): Read examples/_fragments/serializable/java/serializable.md -- modify record declaration (use implements) 6. Nested records (for each attribute with subDtoType=NEW_NESTED_CLASS): Read examples/_fragments/nested-class/java/nested-class.md, "Java record" variant. The skill MUST emit a public record {SubDtoName}(...) { } (NOT a public static class, NOT a separate file). Falling back to NEW_CLASS because the record form looks underspecified is a bug — the record form is fully specified in the fragment.
Recursive component generation for the nested record:
- Auto-select all scalar attributes of the sub-entity (per
references/sub-dto.md, "Auto-selection"). Filter out back-reference associations.
- For each selected scalar, generate a record component using the rules
from Step 3 above (examples/_fragments/fields/java-record.md): {Type} {fieldName} with all inherited validators inlined onto the parameter, space-separated.
- The sub-entity's own associations are NOT expanded recursively. Only
scalars enter the nested record. If the user wants deeper nesting, they must request it explicitly.
- The nested record does NOT need its own skeleton,
@JsonIgnoreProperties,
or Serializable — those apply only to the top-level DTO.
- The inner record's Javadoc uses short name + import (the "Nested
class" variant of examples/_fragments/javadoc/java/javadoc.md).
- The parent record's component for this association uses the nested
{SubDtoName} as its type (e.g. List<SlotDto> slots) — this substitution already happens in Step 3 (fields/java-record.md); no additional editing is needed in Step 6 beyond inserting the inner record itself. 7. Sub-DTO name collision avoidance: if the natural sub-DTO short name already exists in the target package (e.g. PetDto already present), auto-suffix with a number: PetDto1, PetDto2, …. The skill must check list_existing_classes(package) and apply the same suffix rule before generating either a separate-file NEW_CLASS or a NEW_NESTED_CLASS inside a record.
Variant-Specific Questions
No additional questions needed for records. Records are immutable by design:
- allArgsConstructor, equalsHashCode, toString -- all built-in to records, do NOT ask
- mutable=false, fluentSetters=false -- forced
Only ask (prefer AskUserQuestion; fall back to plain text if unavailable):
| Question | Header | Options (first = default) |
|---|---|---|
| @JsonIgnoreProperties(ignoreUnknown=true)? | JsonIgnore | No (Recommended) / Yes |
Notes
- When isJavaRecord=true, allArgsConstructor, equalsHashCode, toString, mutable are disabled (records have them built-in)
- Record components do NOT have
private finalprefix -- justType name
Kotlin DTO
Conditions
- language = KOTLIN
Generation Order
1. Skeleton: Read examples/_skeletons/kotlin/data-class.md (default, isKotlinDataClass=true) or examples/_skeletons/kotlin/class.md (isKotlinDataClass=false — rare) 2. KDoc: Always emit a 3-line block KDoc above the class declaration referencing the entity by fully-qualified name in […] form. Same package does NOT shorten — Kotlin uses FQN, not short-name+import (see references/javadoc.md § Kotlin). 3. Constructor parameters: Read examples/_fragments/fields/kotlin.md — add as primary constructor parameters 4. Nested classes (for each attribute with subDtoType=NEW_NESTED_CLASS): Read examples/_fragments/nested-class/kotlin/nested-class.md 5. Serializable (only when explicitly requested via serializableType): Read examples/_fragments/serializable/kotlin/serializable.md. Default for Kotlin is no Serializable supertype.
Variant-Specific Questions
Prefer AskUserQuestion; fall back to plain text only if the tool is unavailable.
| Question | Header | Options (first = default) |
|---|---|---|
| Mutable DTO (var instead of val)? | Mutable | No (Recommended) / Yes |
The mutable variant only swaps val → var on every primary-constructor parameter — nothing else changes (no no-args constructor, no fluent setters, no separate getters/setters; data-class semantics still apply).
Notes
- Kotlin data classes generate
equals(),hashCode(),toString(),copy(),componentN()automatically. The skill should NOT ask about them and should NOT emit hand-written equals/hashCode/toString. isKotlinDataClass=trueis the default — every Kotlin DTO is adata class. Only emit a plainclassif the user explicitly asks.- Validation annotations always use
@field:target prefix in Kotlin, short-name + import, NOT FQN:@field:NotEmpty, not@field:jakarta.validation.constraints.NotEmpty. - Multiple validators on one field stay on one line with the field, space-separated:
@field:Digits(integer = 10, fraction = 0) @field:NotEmpty val telephone: String. Do NOT line-break per validator. - Validator parameter order is canonical (declaration order from the annotation type), not the order they appeared on the entity. For
@Digitsit isinteger, thenfraction. - Empty validator params → bare annotation (no parentheses):
@field:NotEmpty, not@field:NotEmpty(). Same rule as Java; seereferences/validation.md. - Java types are converted to Kotlin types:
java.lang.String→String,java.lang.Long→Long,java.lang.Integer→Int, etc. - Collection types preserve the entity declaration: if the entity field is
MutableSet<Pet>, the DTO field isMutableSet<PetDto>(orMutableSet<Int>for Flat-id). Do NOT forceList— read the entity collection type and use it as-is. - Nullable types get
?suffix:String?. Default is non-null. - Composite naming for Flat-id sub-DTOs (singular sub-attribute name +
Id/Idssuffix):type: PetType(Flat, onlyidselected) →val typeId: Int;pets: MutableSet<Pet>→val petIds: MutableSet<Int>;specialties: MutableSet<Specialty>→val specialtyIds: MutableSet<Int>. Singular comes from the association name, not the target entity. - Auto-suffix on collision: if
OwnerDtoalready exists in the target package, the next generation usesOwnerDto1, thenOwnerDto2, etc. Same rule as Java (references/java-record.md§ Auto-suffix). - Nested data classes are usually single-line when the constructor only contains short scalars:
data class PetDto(val id: Int, val name: String, val birthDate: LocalDate). Multi-line form is used when the parent class has validation, long parameter lists, or sub-attributes that themselves wrap. - Indentation: 4 spaces inside the constructor parameter list. No leading column.
Sub-DTO Creation Rules
SubDtoType Options
When an entity has association attributes (e.g. @ManyToOne, @OneToOne, @OneToMany, @ManyToMany), each association can be handled differently.
Four options are available, identical for ToOne and ToMany:
| Option | SubDtoType | Notes |
|---|---|---|
| New Class | NEW_CLASS | separate file, sub-entity scalars auto-selected |
| New Nested Class | NEW_NESTED_CLASS | inner class in parent DTO, sub-entity scalars auto-selected |
| Existing Class | EXIST_CLASS | reuse an existing DTO class for the sub-entity |
| Flat | FLAT | inline sub-entity attributes into the parent (see below) |
There is NO "Only ID" option — not for ToOne, not for ToMany, not at any nesting level. The skill must NEVER offer "Only ID" as a separate option. The "association id only" effect is achieved via Flat with only the `id` sub-attribute checked (see "Flat — id-only" below).
The 4 options are the same for ToOne and ToMany; the only ToOne/ToMany difference lies in what Flat produces (composite singular field for ToOne, composite plural collection for ToMany — see below).
Auto-selection of sub-entity attributes when expanding
When the user picks New Class, New Nested Class or Flat on an association, auto-select every scalar attribute of the sub-entity (id, name, …), leaving the sub-entity's own associations unselected. The skill must apply this default. The user is then free to drop individual sub-attributes; in particular, keeping only id is the way to get the "association id only" effect (see Flat below).
Bulk-selection shortcuts
Two bulk-selection modes are supported when including the entity's attributes:
1. Basic attributes — include all scalar (non-association) fields of the root entity. Associations are left out. 2. Basic and association id attributes — include all scalar fields and for every ToOne association, switch it to Flat with only the sub-entity's id selected. ToMany associations are left out.
Treat "include association IDs" as syntactic sugar for "Flat + only id sub-attribute" — the underlying mechanism is the same.
NEW_CLASS (Separate File)
Creates a new DTO file via recursive generation. The sub-DTO inherits ALL parent options:
- equalsHashCode, toString, allArgsConstructor, isMutable, fluentSetters
- isJavaRecord, isJsonIgnoreUnknownProperties, serializableType
The field in the parent DTO uses the sub-DTO short name as its type, and an import line for the sub-DTO is added.
The new file's class-level Javadoc uses the short name + import form (see examples/_fragments/javadoc/java/javadoc.md). This is the same form as every other Javadoc generated — there is no asymmetry between top-level and sub-DTO Javadocs.
NEW_NESTED_CLASS (Inner Class)
Java non-record:
public static class {SubDtoName} {
// fields, constructors, getters/setters -- same rules as parent
}Java record:
public record {SubDtoName}({Type1} {field1}, {Type2} {field2}) {
}Kotlin:
data class {SubDtoName}(
val {field1}: {Type1},
val {field2}: {Type2}
)For Java, also apply the Javadoc fragment (examples/_fragments/javadoc/java/javadoc.md, "Nested class" variant) above the public static class … declaration. The nested-class form uses the short name {@link Pet} plus an import for the sub-entity FQN — same as the top-level form.
EXIST_CLASS (Reuse Existing DTO)
Use this option when the user already has a DTO for the sub-entity that they want to reuse instead of generating a new one.
How to detect candidates
For each association attribute that the skill is about to handle, call list_entity_dtos(subEntityFqn) lazily — the result is the list of EXIST_CLASS candidates for that association.
The call is per-association and on-demand: do not pre-fetch in Step 1 or Step 2 of SKILL.md. The parent entity itself is not called via list_entity_dtos — there is no downstream consumer for that.
How to apply EXIST_CLASS
- Field type in the parent DTO = the chosen existing DTO short name.
- Add an
import {existingDtoFqn};line (unless the existing DTO is in the
same package as the parent DTO).
- Do NOT generate a new file or a new nested class for this attribute.
- For collection associations (
List<Pet>), the field type becomes
List<PetDto> — the collection wrapper stays, only the element type is replaced.
Sanity check
Before substituting, check that the candidate DTO actually targets the sub-entity (its class-level Javadoc {@link …} should reference the sub-entity). If list_entity_dtos returned a class that does NOT match, fall back to NEW_NESTED_CLASS and warn the user.
FLAT (Flatten) — works for BOTH ToOne and ToMany
Flat inlines the chosen sub-entity attributes into the parent DTO. The exact result depends on cardinality and on which sub-attributes the user left checked:
Flat on ToOne (@ManyToOne, @OneToOne)
For each checked scalar sub-attribute, a field is added to the parent with composite singular naming:
{associationName} + {SubAttributeName capitalised}Examples (Pet → PetType):
- Sub-attributes checked:
idonly → parent getsInteger typeId - Sub-attributes checked:
id,name→ parent getsInteger typeId,String typeName
If the user leaves only id checked under Flat, the result is the classic "association id only" form. There is no separate option for this — Flat with id-only IS the mechanism, and the "Basic + association id" bulk-selection shortcut produces exactly this configuration automatically.
Flat on ToMany (@OneToMany, @ManyToMany, List<X>, Set<X>)
For each checked scalar sub-attribute, a collection field is added to the parent with composite plural naming:
singular({associationName}) + {SubAttributeName capitalised} + s → wrapped in the original collection typeExamples (Vet → Specialty, where the field is specialties: Set<Specialty>):
- Sub-attributes checked:
idonly → parent getsSet<Integer> specialtyIds - Sub-attributes checked:
id,name→ parent getsSet<Integer> specialtyIdsANDSet<String> specialtyNames
Examples (Owner → Pet, where the field is pets: List<Pet>):
- Sub-attributes checked:
idonly → parent getsList<Integer> petIds
The collection wrapper (List/Set) is preserved; only the element type is replaced. The base name is the singular form of the association name (specialties → specialty, pets → pet).
Important: Flat works correctly for collections, but only after the user explicitly checks at least one sub-attribute. If the user picks Flat without checking anything, no fields are generated for that association. The skill must auto-check the sub-entity scalars when switching to Flat (same behaviour as for NEW_NESTED_CLASS), so this corner case never occurs.
Filtered fields — back-references
Do NOT offer back-reference @ManyToOne fields in the attribute list. Concretely: Pet has an Owner owner field, but when creating Pet DTO, only offer id, name, birthDate, type, visits — owner is excluded because it points back to a parent entity that owns Pet.
The skill must apply this rule: when listing the entity's attributes via get_entity_details, drop any `@ManyToOne` field whose target entity also has a `@OneToMany` collection of the current entity (this is the parent side). Do not offer such fields to the user.
Asking the User
For each association attribute found via get_entity_details (after filtering back-references), use AskUserQuestion with preview fields showing the concrete code shape for each option.
ToOne (@ManyToOne, @OneToOne)
Use AskUserQuestion with these options (show only Existing Class if list_entity_dtos returned candidates):
| Option | Label | Description |
|---|---|---|
| Flat (Recommended) | Flat — only {type}Id | Most compact form: a single {type}Id field |
| New Nested Class | Nested record/class | Nested class with sub-entity scalars |
| New Class | Separate file | Separate DTO file for the sub-entity |
| Existing Class | Existing class | Reuse an existing DTO |
Default: Flat with only `id` checked — the most compact and common combination.
ToMany (@OneToMany, @ManyToMany, List<X>, Set<X>)
Use AskUserQuestion with these options:
| Option | Label | Description |
|---|---|---|
| New Nested Class (Recommended) | Nested record/class | Nested class with sub-entity scalars |
| New Class | Separate file | Separate DTO file for the sub-entity |
| Existing Class | Existing class | Reuse an existing DTO |
| Flat | Flat — scalar collection | {Collection}<{IdType}> {singular}Ids etc. |
Default: New Nested Class.
In both cases, never offer "Only ID" as a separate option — that option does not exist. It is achieved via Flat-with-id-only.
Validation Annotations
When Applied
Validation annotations are emitted on DTO fields when Hibernate Validator (or Jakarta Validation) is on the classpath. There are two sources of annotations:
1. Inherited from the entity field — every constraint already declared on the corresponding entity attribute. The skill must copy them by default. 2. User-added overrides — the user can ask the skill to add extra constraints (@Email, @Size, @Length, @Pattern, …), tweak parameters (message, min, max, regexp, …), or remove constraints inherited from the entity.
Field rename
The skill must offer per-field rename functionality: accept an optional fieldNameOverride per attribute and use it as the DTO field name (the mapper still maps it from the original entity attribute).
Java Format
Each annotation on a separate line before the field:
@jakarta.validation.constraints.NotNull
@jakarta.validation.constraints.Size(min = 0, max = 255)
private final String name;For records, inline before the component type:
public record MyDto(@jakarta.validation.constraints.NotNull String name) {
}Kotlin Format
Use @field: target prefix:
@field:jakarta.validation.constraints.NotNull @field:jakarta.validation.constraints.Size(min = 0, max = 255) val name: StringAvailable Annotations
Different constraint types are available per field type. The skill must respect the same scoping when offering "extra" constraints — do not suggest @Email on a BigDecimal.
String:
jakarta.validation.constraints.NotNulljakarta.validation.constraints.NotEmptyjakarta.validation.constraints.NotBlankjakarta.validation.constraints.Size(min, max, message)jakarta.validation.constraints.Pattern(regexp, flags, message)jakarta.validation.constraints.Email(regexp, message)jakarta.validation.constraints.Digits(integer, fraction, message)org.hibernate.validator.constraints.Length(min, max, message)org.hibernate.validator.constraints.CodePointLengthorg.hibernate.validator.constraints.URLorg.hibernate.validator.constraints.CreditCardNumberorg.hibernate.validator.constraints.LuhnCheck
Number / Integer / BigDecimal:
jakarta.validation.constraints.NotNulljakarta.validation.constraints.Min(value)jakarta.validation.constraints.Max(value)jakarta.validation.constraints.Digits(integer, fraction)jakarta.validation.constraints.Positivejakarta.validation.constraints.PositiveOrZerojakarta.validation.constraints.Negativejakarta.validation.constraints.NegativeOrZeroorg.hibernate.validator.constraints.Range(min, max)
Date / Temporal:
jakarta.validation.constraints.NotNulljakarta.validation.constraints.Pastjakarta.validation.constraints.PastOrPresentjakarta.validation.constraints.Futurejakarta.validation.constraints.FutureOrPresent
Collections / association references:
jakarta.validation.constraints.NotNulljakarta.validation.constraints.Size(min, max)
Asking the user about per-field overrides
After Step 3 (attribute selection), if the user did NOT say "all defaults", offer per-field customisation via AskUserQuestion (batched, one call per attribute — or skip when the user passes, defaults inherit from the entity).
Use AskUserQuestion with up to 3 questions per attribute:
- "Rename field
{fieldName}in DTO?" (options: No / Yes — enter name) - "Add validations to
{fieldName}?" (multiSelect, options by field type) - "Remove inherited validations?" (multiSelect, options = inherited list)
Prefer AskUserQuestion; fall back to plain text only if the tool is unavailable.
Skip the question entirely if the entity field has no constraints AND the user already said "use defaults".
Rules
- Inherited annotations are kept by default; the user may remove them.
- User-added annotations are merged with inherited ones (no duplicates).
- Use FQN in fragment text — they will be shortened + imported by Step 6.4.
- Preserve parameter ordering exactly as in the source / user request
(use message, regexp for @Pattern, not the alphabetical order from the entity).
- If neither entity nor user provides any constraint, generate no
annotations on the field.
- Empty parameter handling. When the user adds a constraint and leaves
every parameter blank (e.g. @Size with no min, max, or message), emit the bare annotation without parentheses: @Size, not @Size(). Same rule for @Pattern, @Min, @Max, @Digits, etc. — any constraint with all-blank params collapses to its bare form. As soon as at least one parameter has a value, switch to the @Annotation(name = value, …) form and include only the parameters that have values.
Related skills
FAQ
Is Dto Creator safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.