Skip to content
Back to skills

Java Conventions

ASecurity

Use when a ticket adds or changes Java code and it must follow the repo's Java conventions — Effective Java, modern Java (records, sealed types, pattern matching, switch expressions), Optional discipline, immutability, try-with-resources, thread safety and virtual threads, Spring Boot constructor injection, and JUnit 5 + Mockito tests. Invoke for "add this in Java", "fix the Java build", "add a Spring endpoint/service", or as the language pack for any Java change or Java review.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 5, 2026
ai-agentsgojavaexpressspringapidatabasesecurity

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add tmj-90/gaffer --skill java-conventions --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Java Conventions?

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

Security grade badge for Java Conventions
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tmj-90-java-conventions/badge)](https://www.skillsdirectory.com/skills/tmj-90-java-conventions)

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: java-conventions
description: Use when a ticket adds or changes Java code and it must follow the repo's Java conventions — Effective Java, modern Java (records, sealed types, pattern matching, switch expressions), Optional discipline, immutability, try-with-resources, thread safety and virtual threads, Spring Boot constructor injection, and JUnit 5 + Mockito tests. Invoke for "add this in Java", "fix the Java build", "add a Spring endpoint/service", or as the language pack for any Java change or Java review.
stack: [java]
area: language
---

# Write idiomatic, modern Java

Java stays correct when data is immutable, resources close themselves, absence is typed
and shared state is guarded. For the builder and the reviewer of a Java diff; the repo's config and existing code win over it.

## Procedure

1. **Discover the repo's conventions first.** Call `search_lore` for Java conventions.
   Read the build file (`pom.xml` `maven.compiler.release`, or `build.gradle(.kts)`
   toolchain) for the Java version — features below are gated on it — and the wrapper
   (`mvnw`/`gradlew`: always use it). Find the style and analysis tools: Spotless /
   google-java-format, Checkstyle, Error Prone + NullAway, SpotBugs, PMD, and the
   nullness annotations in use (JSpecify `@NullMarked`, JetBrains, jakarta). Note whether
   the repo uses Lombok (`lombok.config`) — follow the repo, do not mix styles. Copy a
   sibling class and its test.
2. **Pin the exact commands** from CI (the `run-tests` and `run-lint` skills). Use the
   defaults below only when the repo defines none.
3. **Write the change with the idioms below**, then walk the concurrency section for
   every executor, shared field, stream, connection and file you touched.
4. **Test each acceptance criterion's own behaviour.** One JUnit 5 test (or
   `@ParameterizedTest` case) per AC that fails without your change, plus its error path.
   If the AC involves shared state or persistence, add a test that runs N concurrent
   callers (an `ExecutorService` plus a `CountDownLatch` start gate) and asserts the
   invariant. Use Testcontainers for real databases where the repo does.
5. **Verify, then stop.** Done when: the formatter check and static analysis are clean, the
   build's verify/check goal is green with the repo's coverage gate, and every AC has a
   test. Record the output with the `record-evidence` skill; the runner submits the work.

## Commands

- Maven: `./mvnw -B verify` (compile, tests, coverage, checks); one test:
  `./mvnw -Dtest='ClassTest#method' test`; format: `./mvnw spotless:check`.
- Gradle: `./gradlew check` (or `build`); one test: `./gradlew test --tests 'pkg.ClassTest'`;
  format: `./gradlew spotlessCheck`; coverage: `./gradlew jacocoTestReport`.
- Dependencies: a new or bumped dependency is a blocker, not an edit to the build file
  (the `dependency-upgrade` skill); `./gradlew dependencies` or `./mvnw dependency:tree`
  shows what is already on the classpath.

## Idioms that matter

- **Records** (16+) for data carriers; validate and defensively copy in the compact
  constructor (`items = List.copyOf(items);`) — a record holding a mutable list or array
  leaks its state.
- **Sealed interfaces + pattern-matching `switch`** (21+) for closed hierarchies, with no
  `default` branch so a new subtype fails to compile.
- **Minimise mutability** (Effective Java 17): `final` fields, `List.of`/`copyOf`, no
  setters on value types; favour composition over inheritance (18).
- **`Optional`** as a return type for "may be absent" (55); never `.get()` without a
  guard (use `orElseThrow`/`map`/`orElseGet`), never for fields or parameters; return
  empty collections, not `null` (54).
- **Nullness**: honour the repo's annotations; `Objects.requireNonNull` at public boundaries.
- **Exceptions**: never ignore one (77); rethrow with the cause
  (`new ServiceException("…", e)`); catch specific types; restore the interrupt flag
  (`Thread.currentThread().interrupt()`) when catching `InterruptedException`.
- **`equals` and `hashCode` together** (11); compare strings and boxed numbers with
  `equals`, never `==`; `BigDecimal` with `compareTo`.
- **Spring**: constructor injection (no field `@Autowired`); thin controllers; `@Valid` +
  Bean Validation on request bodies; `@Transactional` does not apply to self-invocation.
- **Logging**: SLF4J parameterised (`log.info("claimed {}", id)`); no secrets or PII.

## Concurrency and resource safety

- **try-with-resources** for every `AutoCloseable`: streams, readers, JDBC connections,
  `Files.lines`/`Files.walk` streams, and `ExecutorService` (19+).
- **Spring beans are singletons**: a mutable instance field in a `@Service`/`@Controller`
  is shared by every request thread. Keep beans stateless or guard the state.
- **Atomic compound operations**: `ConcurrentHashMap` check-then-put is a race; use
  `compute`/`merge`/`putIfAbsent`; counters use `AtomicLong`/`LongAdder`; a
  read-modify-write holds one lock throughout. `HashMap`, `ArrayList` and
  `SimpleDateFormat` are not thread-safe.
- **Lost updates in the database**: read-modify-write of a row needs `@Version` optimistic
  locking, `SELECT … FOR UPDATE`, or an atomic `UPDATE … SET n = n + 1`.
- **Files other requests read**: write to `Files.createTempFile(targetDir, "x", ".tmp")`
  (unique, same filesystem), then `Files.move(tmp, target, ATOMIC_MOVE, REPLACE_EXISTING)`.
  Cross-process exclusion uses `FileChannel.lock()` or the database; a time-only lease lets
  a paused holder write late, so writes must verify a fencing token or version.
- **Virtual threads** (21+): `Executors.newVirtualThreadPerTaskExecutor()` in
  try-with-resources; never pool them; limit concurrency to a downstream with a
  `Semaphore`, not a pool size; avoid heavy `ThreadLocal` caches. Before JDK 24
  (JEP 491), blocking inside `synchronized` pins the carrier — prefer `ReentrantLock`
  around blocking I/O on 21–23.
- **`CompletableFuture`**: pass an explicit executor (the common pool is shared and small);
  join or handle every future so exceptions are not lost; set timeouts (`orTimeout`).
- **Outbound HTTP** has connect and request timeouts (`HttpClient`, `RestClient`,
  `WebClient` configuration).

## Review checklist — flag as defects

Walk this against the diff. An item is grounds for CHANGES only when, in changed code, it
causes a concrete failure (wrong result, crash, lost or corrupted data, security hole) or
leaves an AC's own behaviour untested: cite the line and that failure. Otherwise it is an
`(optional)` note. Formatting the tools would fix, and preferences the repo
does not enforce (Lombok versus records in a Lombok codebase), are not findings. Do not
patch the code under review.

- [ ] An empty or log-and-continue `catch`; a cause dropped on rethrow; an
      `InterruptedException` swallowed without restoring the flag.
- [ ] `Optional.get()` without a guard; `null` returned where the API promises a value or
      a collection.
- [ ] A resource not in try-with-resources (stream, connection, `Files.lines`, executor).
- [ ] Mutable state in a singleton bean, or a shared non-thread-safe collection/formatter.
- [ ] A check-then-act on a concurrent map, or a read-modify-write (in memory or on a
      row) without a lock, atomic operation or version check.
- [ ] A shared file written in place or via a fixed temp name; a time-only lock lease.
- [ ] A record exposing a mutable component without a defensive copy; `equals` without
      `hashCode`; `==` on strings or boxed values.
- [ ] A switch over a sealed type with a `default` that hides a missing case.
- [ ] Field injection, unvalidated request bodies, or secrets/PII in logs.
- [ ] A `CompletableFuture` never joined, on the common pool for blocking work, or without
      a timeout; an outbound call without timeouts.
- [ ] An AC has no test, or the test mocks away the behaviour the AC describes.

## Capture lore

This skill is one of the places durable, reusable knowledge naturally surfaces:
**A Java convention this repo enforces beyond the obvious — a target version constraint, an immutability or layering rule, a build/formatter gotcha, or a Spring wiring pattern.** That kind of fact is *lore*. Capture it via the **lore-capture
protocol in your brief** (`CLAUDE.factory.md`, step 11 "Memory contribution"):
call the Memory MCP `suggest_lore` once at the close of your work — reusable
conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.

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…