| name | springboot-service-from-template |
| description | Stand up a new HMCTS API-Marketplace service-cp-* using the canonical HMCTS template (service-hmcts-crime-springboot-template) as master source. Use when creating a new Spring Boot service for the Common Platform. |
Spring Boot Service from HMCTS Template
This skill does not generate a Spring Boot app from scratch. It guides
the team through adopting the HMCTS template so the new service stays aligned
with every update that lands in the template over time.
Master source
GitHub: https://github.com/hmcts/service-hmcts-crime-springboot-template
Everything under build.gradle, gradle/, Dockerfile, docker/,
src/main/resources/logback.xml, src/main/resources/application.yaml, and
.github/workflows/ belongs to the template. This skill must not inline
copies of those files — they change, and inline copies rot.
If the template is not already available locally, clone it fresh before
running through the steps below:
git clone https://github.com/hmcts/service-hmcts-crime-springboot-template.git
Context to pull in
Before walking a user through this skill, load these context files:
context/service-shared.md — Spring Boot layering, controllers/services/clients/mappers conventions.
context/azure-sdk-guide.md — Managed Identity, Key Vault, Service Bus.
context/logging-standards.md — mandatory JSON logging.
context/hmcts-standards.md — Coding in the Open, ADR triggers, data protection.
When to use
- User says "create a new HMCTS Spring Boot service", "bootstrap a microservice",
"spin up a new backend" for the Common Platform.
- User is about to start a new
service-cp-* repo.
- User has a repo created from the GitHub template and needs to tailor it.
Do not use this skill for
- API specification repos — use
springboot-api-from-template instead.
- Anything CQRS, RAML, Drools, or WildFly — out of scope for this plugin
entirely. Redirect to
hmcts-sdlc-orchestrator.
API-first — handled inline in Step 2
Every service-cp-* has a matching api-cp-*, and the API repo comes first.
This skill enforces that operationally in Step 2 — it asks for the API
repo name, offers to derive it from the service name by convention, checks
the repo exists, and if it doesn't, delegates to springboot-api-from-template
to create it before the service repo is ever touched.
- Pair convention: if the service is
service-cp-crime-caseadmin-hearings,
the API repo is api-cp-crime-caseadmin-hearings — same suffix.
- The service consumes the API via the
apiSpec configuration in
build.gradle — see Step 8 below.
The rationale is captured in context/hmcts-standards.md → "API-first
design". Do not collapse contract and implementation into one repo.
Required input
Ask the user (one question at a time is fine; batch if they've already volunteered some):
- Service name — must follow
service-cp-[case-type]-{business-domain}-{name-of-entity}.
case-type (optional): civil | crime | family | tribunal.
business-domain: e.g., caseingestion, caseadmin, casehearing, schedulingandlisting.
- Forbidden tokens:
common, core, base, utils, helpers, misc, shared.
- Owning GitHub team slug — the team that will own the repo. Prerequisite — the repo will not be created without one. The team must already exist in the
hmcts org; if it does not, stop and ask the org admins to create the team first. Optional: a secondary team slug plus its permission (usually push).
- Short description — one-line summary used on the GitHub repo and in the new
README.md.
- Java package root —
uk.gov.hmcts.cp.{business-domain}.{entity}.
- Upstream / downstream — which services does this call, and which call it?
- Stateful? — does it need Postgres? (Flyway + Testcontainers wiring is already in the template.)
- Azure integrations expected — Service Bus topics/queues, Key Vault references, App Configuration usage. Trigger
azure-sdk-guide.md if any are planned.
- Ownership context — on-call rota, support model, escalation path.
The matching API repo name and published artefact version are captured
interactively in Step 2 — do not ask for them here.
GitHub owner is not a user input. Default hmcts.
Repo visibility is not a user input. Public, per "Coding in the Open" — no exception without an ADR.
Process
Step 1 — Validate the owning GitHub team exists
gh api /orgs/hmcts/teams/{team-slug} --jq '.slug, .name'
Stop if Not Found — do not fall back to creating the repo with only a user as admin.
Step 2 — Confirm (or create) the matching API repo
- Ask: "What's the API repo name for this service?" Offer the derived
default (
service- → api- prefix swap). Capture as {api-name}.
- Check existence:
gh repo view hmcts/{api-name} --json name,visibility,isTemplate 2>/dev/null
- Exists → ask for the published SemVer artefact version to depend on.
Record
uk.gov.hmcts.cp:{api-name}:{version} for Step 8.
- Not found → halt this skill's flow and invoke
springboot-api-from-template
with {api-name}, passing through owning team/owner/description already
collected. After it completes (repo exists, spec drafted, first version
published), resume here with the new coordinate. If it cannot complete
(team doesn't exist yet, consumer review still open), surface the blocker
— do not create the service repo and do not wire a placeholder coordinate.
Important — never fall back to the template's own placeholder coordinate
(uk.gov.hmcts.cp:api-hmcts-crime-template:X.Y.Z); it is not a valid
production dependency.
Step 3 — Create the repo from the template (via GitHub API)
gh repo create hmcts/{service-name} \
--template hmcts/service-hmcts-crime-springboot-template \
--public \
--description "{short description}" \
--clone
Step 4 — Grant the owning team access
gh api --method PUT \
/orgs/hmcts/teams/{team-slug}/repos/hmcts/{service-name} \
-f permission=admin
Secondary team (if any) gets push:
gh api --method PUT \
/orgs/hmcts/teams/{secondary-team-slug}/repos/hmcts/{service-name} \
-f permission=push
Verify:
gh api /repos/hmcts/{service-name}/teams --jq '.[] | {slug, permission}'
Step 4.5 — Verify the team actually has members (new)
gh api /orgs/hmcts/teams/{team-slug}/members --jq '.[].login'
If empty, surface a warning: "{team-slug} has admin access to
{service-name} but has zero members — no one can push until someone is
added to the team." Don't block creation on it, but always surface it.
Step 5 — Post-creation bookkeeping
gh repo view hmcts/{service-name} --json visibility,templateRepository
Enable "Automatically delete head branches", then import the main-branch
ruleset via the API, not the GitHub UI's "New ruleset" flow (same as the
API skill's Step 6) — gh repo create --template does not carry rulesets
over, and clicking through "New ruleset" in the UI defaults
required_approving_review_count to 0, silently leaving the new repo
mergeable without review. This was found live on a freshly-created
service-cp-* repo whose ruleset had required_approving_review_count: 0
despite the template's own .github/rulesets/main.json correctly specifying
1 — the gap is in how the ruleset gets imported, not the template file:
gh api --method POST repos/hmcts/{service-name}/rulesets \
--input service-hmcts-crime-springboot-template/.github/rulesets/main.json
(Adjust the --input path to wherever the template was cloned/checked out.
Do not hand-recreate the ruleset's JSON via UI clicks or -f/-F flags —
gh api -F does not reliably build the nested rules[] array of objects;
always import the template's actual file with --input.)
Step 5.5 — Verify the imported ruleset, not just its presence (new)
gh api repos/hmcts/{service-name}/rulesets \
--jq '.[] | select(.name=="main") | .rules[] | select(.type=="pull_request") | .parameters.required_approving_review_count'
This must print 1 (or whatever non-zero value the template specifies) —
never 0. If it prints 0, the import silently dropped or zeroed the
field; do not just flag it, fix it immediately by re-importing
(gh api --method PUT repos/hmcts/{service-name}/rulesets/{id} --input {the-template-json}) and re-check. A 0 value is always a bug, not a policy
trade-off — unlike the deadlock check below, it is safe to fix without asking.
Separately, compare the (now-verified-non-zero) count against the Step 4.5
member count; flag — don't silently fix — if the required review count would
deadlock the team's own PRs (e.g. a 1-person team needing 2 approvals). That
one is a policy trade-off for the user to decide.
Step 6 — Read what the template already gives you
Read, in this order — do not modify yet: README.md, build.gradle +
gradle/*.gradle, src/main/resources/application.yaml,
src/main/resources/logback.xml, Dockerfile, .github/workflows/,
docs/SpringUpgradev4.md, docs/EnvironmentVariables.md, docs/JWTFilter.md,
docs/Logging.md, docs/PIPELINE.md.
Step 7 — Service identity
settings.gradle — rootProject.name → the service name.
application.yaml — spring.application.name and
management.metrics.tags.service → the real name.
- Rename the Java base package to
uk.gov.hmcts.cp.{business-domain}.{entity}.
- Rename
Application.java references accordingly.
- Delete
Example* sample code and tests. Keep GlobalExceptionHandler,
RootController, filters, config.
- Update
README.md — purpose, owners, runbook link, escalation, and add
the "New team member setup" section below. While editing it, strip the
template's generic boilerplate rather than carrying it into every new
repo verbatim:
- Delete the "About this template", "Want to Build Your Own Path?", and
"Implementation Patterns & Demo Project" sections (the full demo-repo
table). They describe the template, not this service — link to the
template README
and its docs
instead of inlining them.
- Delete the inline "Prerequisites" / "Installation" / "Static code
analysis" sections (Java/Gradle/PMD setup) for the same reason — generic
build instructions belong in the template's own README, not duplicated
per repo.
- Delete
docs/PIPELINE.md and docs/SpringUpgradev4.md from the new
repo (or any other doc that narrates the template's own development
history rather than this service) — they reference classes and
decisions from the template's lineage that don't exist in the new repo,
and rot immediately. Keep only docs that are genuinely about this
service (e.g. docs/Logging.md if customised).
- Keep everything that's actually specific to this repo: the API
contract, upstream/downstream calls, owning team, support model, and
the "New team member setup" section.
- Reference: this exact trim was done after-the-fact for
service-cp-crime-hearing — see its README/PR history for the target
shape. Doing it here at scaffold time avoids a repeat cleanup PR.
Do not change the build.gradle plugin block, apply from: list,
logback.xml encoder/providers, or the Dockerfile non-root setup.
Step 8 — Wire the API spec dependency (apiSpec)
Replace the placeholder coordinate with the one captured in Step 2:
dependencies {
apiSpec "uk.gov.hmcts.cp:{api-name}:{semver}"
...
}
Exactly one apiSpec line per service. If multiple APIs seem needed, stop
and raise an ADR — that's usually a service-boundary smell.
Step 9 — Environment variables and secrets
Per docs/EnvironmentVariables.md: .env + .envrc via direnv locally
(gitignored), all runtime config env-driven, Managed Identity for Azure auth,
secrets from Key Vault, SERVER_PORT default 8082.
Step 10 — Persistence (only if stateful)
Flyway migrations under src/main/resources/db/migration/V{n}__{description}.sql,
JPA entities + Spring Data repositories, MapStruct DTO mapping, Testcontainers
for integration tests.
Step 11 — CI/CD
Activate .github/workflows/ci-build-publish.yml, ci-draft.yml,
ci-released.yml, CodeQL, Trufflehog, gitleaks, PMD, ACR publish per
docs/GITHUB-ACTIONS.md. Do not fork the workflows.
Step 12 — Helm + Flux CD
Helm chart with liveness/readiness probes, runAsNonRoot: true,
Workload Identity annotation + federated credential, register in
cpp-flux-config.
Step 13 — Observability wiring
OpenTelemetry starter already on the classpath; set OTEL_TRACES_URL /
OTEL_METRICS_URL via Helm; App Insights agent injected by the base image.
Step 14 — First-run verification
./gradlew build
docker compose up
./gradlew bootRun
curl -i http://localhost:8082/actuator/health/readiness
curl -i http://localhost:8082/
Confirm stdout is JSON-parseable with correlationId/requestId in MDC.
Step 15 — ADR any deviation
Use the adr-template skill for any deviation from the template.
New team member setup
Anyone newly added to the owning team needs to do this once, to catch any
gap between "team has repo access" and "this person can actually push":
gh auth login
git clone git@github.com:hmcts/{service-name}.git
cd {service-name}
git checkout -b smoke/access-check
git commit --allow-empty -m "chore: verify push access"
git push -u origin smoke/access-check
git push origin --delete smoke/access-check
If the push is rejected with a permissions error, re-check Step 4 (team
grant) and Step 4.5 (team membership) before assuming a tooling problem.
Quick-check before marking "done"
Divergence policy
The template is the master source. Propose changes upstream; do not fork
defaults locally.