Skip to content
Back to skills

Spring Rest Endpoint

ASecurity

Scaffold a Spring Web REST endpoint correct-by-default — controller + request/ response DTOs + service method + mapper. Never exposes a JPA entity (satisfies the entity-in-controller guardrail). Request bodies are @Valid DTOs. Trigger phrases: "add endpoint", "scaffold rest controller", "new REST endpoint", "create controller".

  • 10 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added August 31, 2026
developmentjavaspringapi

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned August 31, 2026

npx -y skills add ShipWithAI/shipwithai-plugins --skill spring-rest-endpoint --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Spring Rest Endpoint?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Spring Rest Endpoint
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shipwithai-spring-rest-endpoint/badge)](https://www.skillsdirectory.com/skills/shipwithai-spring-rest-endpoint)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: spring-rest-endpoint
description: >
  Scaffold a Spring Web REST endpoint correct-by-default — controller + request/
  response DTOs + service method + mapper. Never exposes a JPA entity (satisfies the
  entity-in-controller guardrail). Request bodies are @Valid DTOs.
  Trigger phrases: "add endpoint", "scaffold rest controller", "new REST endpoint",
  "create controller".
argument-hint: "[ResourceName]"
---

# Spring REST Endpoint — Scaffold

Generates a controller that returns **DTOs only**, so the `entity-in-controller`
guardrail never fires. Output: controller, response DTO, request DTO, a service
method, and an entity→DTO mapper.

## Step 1 — Detect conventions

- Base package: infer from `src/main/java/.../` (the deepest package holding existing
  `*Controller.java`, else the application's root package).
- Existing patterns: if the project already has a `mapper`/`dto` package or MapStruct
  on the classpath, match it; otherwise generate a plain hand-written mapper.

## Step 2 — Gather the resource

Ask (or take from argument): resource name (e.g. `Order`), and which operations
(`GET /{id}`, `GET` list, `POST`, `PUT`, `DELETE`). Default to read + create if unspecified.

## Step 3 — Render templates

From `assets/`, substitute `{{PACKAGE}}`, `{{RESOURCE}}`, `{{RESOURCE_LOWER}}`:

- `Controller.java.tmpl`  → `<pkg>/web/{{RESOURCE}}Controller.java`
- `ResponseDto.java.tmpl` → `<pkg>/web/dto/{{RESOURCE}}Response.java`
- `RequestDto.java.tmpl`  → `<pkg>/web/dto/{{RESOURCE}}Request.java`

Rules baked into the templates:
- Controller methods return `{{RESOURCE}}Response` / `List<{{RESOURCE}}Response>` /
  `ResponseEntity<{{RESOURCE}}Response>` — **never the entity**.
- `@PostMapping`/`@PutMapping` take `@Valid @RequestBody {{RESOURCE}}Request`.
- A `{{RESOURCE}}Mapper` (or inline mapping in the service) converts entity → response.

## Step 4 — Wire the service

If a `{{RESOURCE}}Service` exists, add the method; otherwise generate a minimal
service that depends on the repository and returns the mapped DTO. Keep the
`@Transactional` boundary on the **public** service method (not the controller).

## Variant — Cursor (keyset) pagination

For list endpoints that page large or frequently-written tables, prefer **cursor**
pagination over `OFFSET`/`Pageable`. Offset re-scans and skips the leading rows
(slower as the page number grows) and **skips or duplicates** rows when concurrent
inserts/deletes shift the offset between requests. Keyset paging is stable because
it pages by the last seen key, not a row count.

Render `assets/CursorPage.java.tmpl` → `<pkg>/web/dto/CursorPage.java`, then
expose `GET /api/{{RESOURCE_LOWER}}s?cursor=&size=` returning `CursorPage<{{RESOURCE}}Response>`:

```java
// Repository — page by the last seen id (keyset), not OFFSET.
@Query("select e from {{RESOURCE}} e where e.id > :cursor order by e.id")
List<{{RESOURCE}}> findAfter(@Param("cursor") long cursor, Pageable limit);

// Service — fetch size+1 is optional; here nextCursor = last id, or null when short.
List<{{RESOURCE}}> rows = repo.findAfter(cursor, PageRequest.of(0, size));
String next = rows.size() < size ? null : String.valueOf(rows.get(rows.size() - 1).getId());
return new CursorPage<>(map(rows), next);
```

The client passes `cursor=0` (or omits it) for the first page and echoes `nextCursor`
until it comes back null. Use the table's primary key (or any monotonic, indexed
column) as the cursor so the `where … > :cursor` scan stays index-backed.

## Step 5 — Report

List created files and remind: the controller never returns the entity; map in the
service/mapper. If the resource has no entity yet, suggest `/jpa-entity` first.

## Failure modes — do NOT

- Do NOT return, accept, or expose a JPA `@Entity` in any controller signature.
- Do NOT put `@Transactional` on the controller.
- Do NOT skip `@Valid` on request bodies.
- Do NOT overwrite an existing controller without showing a diff and confirming.

Files in this skill

  • SKILL.md3.9 KB
  • assets/Controller.java.tmpl1.2 KB
  • assets/CursorPage.java.tmpl603 B
  • assets/RequestDto.java.tmpl338 B
  • assets/ResponseDto.java.tmpl383 B
  • evals.json2.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.