Java (Spring Boot)

Spring Boot services.

What this is

Spring Boot services. Persistence correctness dominates the BLOCKER tier — entities leaking out of controllers, N+1 queries, @Transactional silently doing nothing on self-invocation, string-concatenated JPQL. The rest is Spring's DI and concurrency model done wrong: field injection over constructor injection, mutable state in singleton beans, blocking calls inside a reactive WebFlux pipeline.

How to onboard it

A repository picks up this stack by onboarding on a profile that includes it. redline init detects the profile from what is in the repository, so in most cases this is automatic:

terminal
$ npx redlinegate init  # detects the profile
$ npx redlinegate init --profile service-java  # or name one

One profile pulls these rules in: service-java.

Once onboarded, your files match this stack when they fit any of these globs:

  • **/*.java

How to use it — 20 rules

Nothing to run. Once your profile includes Java (Spring Boot), redline init renders these rules into your repository's AI tooling and the reviewer applies them on every pull request. When a review comment cites one of these ids, this table is where to look up what it catches and why.

Rule idSeverityCatches
java/entity-in-controller-responseBLOCKERJPA entities returned from controllers. Endpoints return DTOs; entities leak lazy-loading proxies, internal fields, and cause serialization surprises.
java/n-plus-one-queriesBLOCKERN+1 queries. Lazy association accessed in a loop or during serialization — require fetch join, `@EntityGraph`, or a projection.
java/transactional-self-invocationBLOCKER`@Transactional` on private/self-invoked methods — proxy is bypassed, annotation silently does nothing.
java/broad-catchBLOCKERCatching `Exception`/`Throwable` broadly and continuing — catch specific types; rethrow or fail the operation.
java/field-injectionBLOCKERField injection (`@Autowired` on fields) in new code — constructor injection only; field injection breaks testability and hides dependencies.
java/mutable-singleton-stateBLOCKERMutable shared state in singleton beans without synchronization — services/components are singletons; instance fields must be immutable or thread-safe.
java/blocking-in-reactiveBLOCKERBlocking calls inside reactive pipelines (WebFlux: `block()`, JDBC, `RestTemplate` inside `Mono`/`Flux` chains).
java/jpql-string-concatenationBLOCKERString-concatenated JPQL/SQL with external input — parameterized queries / criteria API only.
java/optional-misuseHIGH`Optional` misuse: `Optional.get()` without presence check, `Optional` as field or method parameter.
java/entity-equals-hashcodeHIGH`equals`/`hashCode` on JPA entities using generated IDs incorrectly (breaks in sets before persist) — or missing entirely on value objects.
java/missing-bean-validationHIGHMissing `@Valid` + Bean Validation on request DTOs at controller boundary.
java/transaction-scope-too-wideHIGHTransaction scope too wide: external HTTP calls or message publishing inside `@Transactional` — holds connections, couples commit to remote latency.
java/async-without-executorHIGH`@Async`/`CompletableFuture` work without dedicated executor — default pool starvation.
java/exception-detail-in-responseHIGHException details (stack traces, SQL) returned in API error responses — map to problem-detail responses.
java/legacy-date-apiHIGHNew Date/`Calendar`/`SimpleDateFormat` in new code — `java.time` only; `SimpleDateFormat` is not thread-safe.
java/stream-side-effectsHIGHStreams with side effects (`forEach` mutating external collections) — collect instead.
java/lombok-data-on-entitySUGGESTIONLombok `@Data` on entities (equals/hashCode/toString pitfalls) — prefer `@Getter` + explicit methods, or records for DTOs.
java/records-for-dtosSUGGESTIONRecords for immutable DTOs where Java version allows.
java/var-usageSUGGESTION`var` for obviously-typed locals; explicit types where inference hurts readability.
java/magic-valuesSUGGESTIONMagic numbers/strings for business values — extract named constants.

Expected output

A finding from this file, and every finding Redline produces, opens with a machine-readable first line — severity, then the rule id in brackets, then the problem in one line:

a finding from this file
Redline/BLOCKER [java/entity-in-controller-response]: <one-line problem>

Ids are aggregated per rule, which is how the organisation finds out which rules earn their place and which only generate noise — so a finding without a valid id cannot be measured and counts as untagged. Of the 20 rules here, 8 BLOCKER, 8 HIGH and 4 SUGGESTION. Only a BLOCKER must not merge; a SUGGESTION may be dismissed without justification, and is never upgraded to get attention.

If a rule here fires constantly on code your team has deliberately decided to allow, that is the signal to raise with the standards owner — the rule id is what makes that conversation measurable — not to argue it away comment by comment.

How to edit it

Rules are edited in standards/stacks/java.md and nowhere else. The rendered copies in AGENTS.md, .github/copilot-instructions.md and .github/instructions/ are generated and are overwritten by the next render. A change here propagates to every onboarded repository as a pull request, so treat it as a production change.

  1. Edit the markdownstandards/ is the only place a human edits a rule. Everything under AGENTS.md, .github/copilot-instructions.md and .github/instructions/ is rendered from it and is overwritten by the next render.
  2. node scripts/assign-rule-ids.mjsAssigns a permanent <stack>/<slug> id to any new rule bullet and rewrites the file in place. Do not invent an id by hand. CI runs the same script with --check and fails if a rule is missing one.
  3. node scripts/render-self.mjsRe-renders this repository's own artifacts from the edited source. CI runs it with --check, so stale checked-in output fails the build.
  4. Bump standards/manifest.json → versionRequired in the same pull request as the rule change. Sync pull requests quote the version, so a repository's rendered artifacts always name where they came from.
  5. Add a CHANGELOG.md entryAlso in the same pull request. A standards change with no measurement is an opinion — record the seed score alongside it.
  6. node scripts/validate.mjsThe bundle self-check CI runs: manifest integrity, well-formed rule ids, the severity output contract surviving your edit, glob portability.

Rule ids are permanent. Rewording a rule is fine and keeps its id; renaming or removing an id orphans every historical telemetry record that cited it.

The full file

standards/stacks/java.md · 31 lines · 2.9 KB
# Java (Spring Boot) Review Rules

## BLOCKER — request changes

- `java/entity-in-controller-response` — **JPA entities returned from controllers.** Endpoints return DTOs; entities leak lazy-loading proxies, internal fields, and cause serialization surprises.
- `java/n-plus-one-queries` — **N+1 queries.** Lazy association accessed in a loop or during serialization — require fetch join, `@EntityGraph`, or a projection.
- `java/transactional-self-invocation` — **`@Transactional` on private/self-invoked methods** — proxy is bypassed, annotation silently does nothing.
- `java/broad-catch` — **Catching `Exception`/`Throwable` broadly and continuing** — catch specific types; rethrow or fail the operation.
- `java/field-injection` — **Field injection (`@Autowired` on fields)** in new code — constructor injection only; field injection breaks testability and hides dependencies.
- `java/mutable-singleton-state` — **Mutable shared state in singleton beans** without synchronization — services/components are singletons; instance fields must be immutable or thread-safe.
- `java/blocking-in-reactive` — **Blocking calls inside reactive pipelines** (WebFlux: `block()`, JDBC, `RestTemplate` inside `Mono`/`Flux` chains).
- `java/jpql-string-concatenation` — **String-concatenated JPQL/SQL with external input** — parameterized queries / criteria API only.

## HIGH

- `java/optional-misuse` — `Optional` misuse: `Optional.get()` without presence check, `Optional` as field or method parameter.
- `java/entity-equals-hashcode` — `equals`/`hashCode` on JPA entities using generated IDs incorrectly (breaks in sets before persist) — or missing entirely on value objects.
- `java/missing-bean-validation` — Missing `@Valid` + Bean Validation on request DTOs at controller boundary.
- `java/transaction-scope-too-wide` — Transaction scope too wide: external HTTP calls or message publishing inside `@Transactional` — holds connections, couples commit to remote latency.
- `java/async-without-executor` — `@Async`/`CompletableFuture` work without dedicated executor — default pool starvation.
- `java/exception-detail-in-response` — Exception details (stack traces, SQL) returned in API error responses — map to problem-detail responses.
- `java/legacy-date-api` — New Date/`Calendar`/`SimpleDateFormat` in new code — `java.time` only; `SimpleDateFormat` is not thread-safe.
- `java/stream-side-effects` — Streams with side effects (`forEach` mutating external collections) — collect instead.

## SUGGESTION

- `java/lombok-data-on-entity` — Lombok `@Data` on entities (equals/hashCode/toString pitfalls) — prefer `@Getter` + explicit methods, or records for DTOs.
- `java/records-for-dtos` — Records for immutable DTOs where Java version allows.
- `java/var-usage` — `var` for obviously-typed locals; explicit types where inference hurts readability.
- `java/magic-values` — Magic numbers/strings for business values — extract named constants.