/java
Always load this skill when writing, modifying, creating, or moving Java or Kotlin source code, or when project setup has already chosen Java/Kotlin as the implementation language.
Claude Code
$ npx -y skills add t1/tdder --skill java --agent claude-code
Codex
$ npx skills add t1/tdder --skill java -a codex -y
Cursor
$ npx skills add t1/tdder --skill java -a cursor -y
Gemini CLI
$ npx skills add t1/tdder --skill java -a gemini-cli -y
GitHub Copilot
$ npx skills add t1/tdder --skill java -a github-copilot -y
OpenCode
$ npx skills add t1/tdder --skill java -a opencode -y
Windsurf
$ npx skills add t1/tdder --skill java -a windsurf -y
SKILL.md
java.SKILL.mdname: java
description: >
Always load this skill when writing, modifying, creating, or moving Java or Kotlin source code,
or when project setup has already chosen Java/Kotlin as the implementation language.
version: 0.3.0
Java & Kotlin Conventions
JVM language coding conventions complementing the TDD and Clean Code skills. We use modern language features to reduce boiler plate and get more to the point.
Java Version
When you need to know the latest Java version (e.g. for new projects, upgrades, or Dockerfiles), query the Adoptium API:
https://api.adoptium.net/v3/info/available_releases
Use `most_recent_feature_release` (latest GA) by default. Use `most_recent_lts` only when explicitly asked for LTS.
Code Style
Logic Expressions
- Try to prevent negating expressions, e.g., instead of `a != b ? doX() : doY()`
use `a == b ? doY() : doX()`.
Line Breaks
- Annotations can often go in the same line as the field or method they apply to.
- Java: `@Override void foo() {` or `@Test void should() {`
- Kotlin: `override fun foo() {` or `@Test fun should() {`
Local Type Inference
**Java:** Use `var` for local variable type inference where the type is clear from context.
var users = userRepository.findAll();
var count = items.size();
**Kotlin:** Type inference is the default. Prefer `val` (immutable) over `var` (mutable); only use `var` when the variable must be reassigned.
val users = userRepository.findAll()
var count = items.size // only if count is later mutated
Static Imports (Java) / Imports (Kotlin)
**Java:** Prefer static imports when they do not reduce readability. This includes constants like `MediaType.APPLICATION_JSON`. The context is most often sufficient to understand what it is, e.g., `@Produces(APPLICATION_JSON)`. Exception: do not statically import `List.of(...)`, `Map.of(...)`, or the like, as a method name like `of` doesn't say, what it does. The number of usages of the import is **not** relevant!
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.BDDMockito.given;
**Kotlin:** There are no static imports. Import top-level functions and object/companion members directly. The same readability rule applies — import short, context-sufficient names; avoid importing bare `of`.
import jakarta.ws.rs.core.MediaType.APPLICATION_JSON
import org.assertj.core.api.BDDAssertions.then
import org.mockito.kotlin.given
Javadoc / KDoc
**IMPORTANT** Use **Markdown** for doc comments to improve in-IDE readability. Java uses Javadoc (`/** */`); Kotlin uses KDoc (same `/** */` syntax, same Markdown support).
Comments
Add comments only if they add real value. Prefer self-explanatory code. Comments should explain "why", not "what".
Exception Handling
**Java:** Hide checked exceptions within a method by wrapping them in `RuntimeException` and add a helpful message:
try{
return objectMapper.writeValueAsString(value);
} catch(JsonProcessingException e) {
throw new RuntimeException("could not write value as string",e);
}**Kotlin:** Has no checked exceptions, so no wrapping is needed. When calling Java APIs that declare checked exceptions, Kotlin treats them as unchecked — handle them only if recovery makes sense.
Log Output
`System.out` output is only acceptable for "normal" output in CLI tools. `System.err` output is only acceptable for warnings and diagnostics (logs) in CLI tools. In all other cases, use some logging API (preferably slf4j) and if necessary a library (preferably logback).
Constructors and Dependency Injection
When available, use the capabilities of a Dependency Injection framework like CDI. Even though constructor injection is not commonly used in CDI, it makes, e.g., unit-testing (when DI is *not* available) easier than field injection does. Injecting with setters is very seldom a sensible option to choose.
If the value of a field doesn't depend on constructor parameters, then use a **Field initializer**:
- Java: `private final HttpClient httpClient = HttpClient.newHttpClient();`
- Kotlin: `val httpClient = HttpClient.newHttpClient()`
Visibility
Only well-defined APIs are `public`. Internals are not, but have limited visibility, i.e. `private` if possible. If wider than `private` is needed:
- Java: use package-private (no modifier)
- Kotlin: use `internal` (module-visible)
`protected` is only rarely necessary in either language.
Testing Conventions
BDD Naming
Use BDD-style method names.
Java:
@Test void shouldParseSemanticVersion() { ... }
@Test void shouldReturnEmptyForInvalidInput() { ... }Kotlin:
@Test fun shouldParseSemanticVersion() { ... }
@Test fun shouldReturnEmptyForInvalidInput() { ... }BDD Assertions
For verifications, use `assertj-core`.
Use `then(...)` instead of `assertThat(...)`:
then(result).isEqualTo(expected);
then(list).hasSize(3).containsExactly("a","b","c");BDD Mockito
Use `given(...).willReturn(...)` instead of `when(...).thenReturn(...)`. Place Mockito `given()` calls in the "given" block:
given(repository.findById(id)).willReturn(Optional.of(entity));
var result = service.process(id);
then(result).isNotNull();
If the `then` of AssertJ is imported, too, fall back to `verify`.
Test Block Formatting
- Always use an **empty line** to separate given, when, and then blocks (no comments)
- Within these blocks, do **not** add empty lines between statements
Java:
@Test void shouldCalculateTotal() {
given(taxService.rate()).willReturn(0.1);
var items = List.of(new Item(10), new Item(20));
var total = calculator.calculate(items);
then(total).isEqualTo(33.0);
}Kotlin:
@Test fun shouldCalculateTotal() {
given(taxService.rate()).willReturn(0.1)
val items = listOf(Read more
name: java description: > Always load this skill when writing, modifying, creating, or moving Java or Kotlin source code, or when project setup has already chosen Java/Kotlin as the implementation language. version: 0.3.0
Java & Kotlin Conventions
JVM language coding conventions complementing the TDD and Clean Code skills. We use modern language features to reduce boiler plate and get more to the point.
Java Version
When you need to know the latest Java version (e.g. for new projects, upgrades, or Dockerfiles), query the Adoptium API:
https://api.adoptium.net/v3/info/available_releases
Use `most_recent_feature_release` (latest GA) by default. Use `most_recent_lts` only when explicitly asked for LTS.
Code Style
Logic Expressions
- Try to prevent negating expressions, e.g., instead of `a != b ? doX() : doY()`
use `a == b ? doY() : doX()`.
Line Breaks
- Annotations can often go in the same line as the field or method they apply to.
- Java: `@Override void foo() {` or `@Test void should() {`
- Kotlin: `override fun foo() {` or `@Test fun should() {`
Local Type Inference
**Java:** Use `var` for local variable type inference where the type is clear from context.
var users = userRepository.findAll(); var count = items.size();
**Kotlin:** Type inference is the default. Prefer `val` (immutable) over `var` (mutable); only use `var` when the variable must be reassigned.
val users = userRepository.findAll() var count = items.size // only if count is later mutated
Static Imports (Java) / Imports (Kotlin)
**Java:** Prefer static imports when they do not reduce readability. This includes constants like `MediaType.APPLICATION_JSON`. The context is most often sufficient to understand what it is, e.g., `@Produces(APPLICATION_JSON)`. Exception: do not statically import `List.of(...)`, `Map.of(...)`, or the like, as a method name like `of` doesn't say, what it does. The number of usages of the import is **not** relevant!
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.BDDMockito.given;
**Kotlin:** There are no static imports. Import top-level functions and object/companion members directly. The same readability rule applies — import short, context-sufficient names; avoid importing bare `of`.
import jakarta.ws.rs.core.MediaType.APPLICATION_JSON import org.assertj.core.api.BDDAssertions.then import org.mockito.kotlin.given
Javadoc / KDoc
**IMPORTANT** Use **Markdown** for doc comments to improve in-IDE readability. Java uses Javadoc (`/** */`); Kotlin uses KDoc (same `/** */` syntax, same Markdown support).
Comments
Add comments only if they add real value. Prefer self-explanatory code. Comments should explain "why", not "what".
Exception Handling
**Java:** Hide checked exceptions within a method by wrapping them in `RuntimeException` and add a helpful message:
try{
return objectMapper.writeValueAsString(value);
} catch(JsonProcessingException e) {
throw new RuntimeException("could not write value as string",e);
}**Kotlin:** Has no checked exceptions, so no wrapping is needed. When calling Java APIs that declare checked exceptions, Kotlin treats them as unchecked — handle them only if recovery makes sense.
Log Output
`System.out` output is only acceptable for "normal" output in CLI tools. `System.err` output is only acceptable for warnings and diagnostics (logs) in CLI tools. In all other cases, use some logging API (preferably slf4j) and if necessary a library (preferably logback).
Constructors and Dependency Injection
When available, use the capabilities of a Dependency Injection framework like CDI. Even though constructor injection is not commonly used in CDI, it makes, e.g., unit-testing (when DI is *not* available) easier than field injection does. Injecting with setters is very seldom a sensible option to choose.
If the value of a field doesn't depend on constructor parameters, then use a **Field initializer**:
- Java: `private final HttpClient httpClient = HttpClient.newHttpClient();`
- Kotlin: `val httpClient = HttpClient.newHttpClient()`
Visibility
Only well-defined APIs are `public`. Internals are not, but have limited visibility, i.e. `private` if possible. If wider than `private` is needed:
- Java: use package-private (no modifier)
- Kotlin: use `internal` (module-visible)
`protected` is only rarely necessary in either language.
Testing Conventions
BDD Naming
Use BDD-style method names.
Java:
@Test void shouldParseSemanticVersion() { ... }
@Test void shouldReturnEmptyForInvalidInput() { ... }Kotlin:
@Test fun shouldParseSemanticVersion() { ... }
@Test fun shouldReturnEmptyForInvalidInput() { ... }BDD Assertions
For verifications, use `assertj-core`.
Use `then(...)` instead of `assertThat(...)`:
then(result).isEqualTo(expected);
then(list).hasSize(3).containsExactly("a","b","c");BDD Mockito
Use `given(...).willReturn(...)` instead of `when(...).thenReturn(...)`. Place Mockito `given()` calls in the "given" block:
given(repository.findById(id)).willReturn(Optional.of(entity)); var result = service.process(id); then(result).isNotNull();
If the `then` of AssertJ is imported, too, fall back to `verify`.
Test Block Formatting
- Always use an **empty line** to separate given, when, and then blocks (no comments)
- Within these blocks, do **not** add empty lines between statements
Java:
@Test void shouldCalculateTotal() {
given(taxService.rate()).willReturn(0.1);
var items = List.of(new Item(10), new Item(20));
var total = calculator.calculate(items);
then(total).isEqualTo(33.0);
}Kotlin:
@Test fun shouldCalculateTotal() {
given(taxService.rate()).willReturn(0.1)
val items = listOf(A plugin for pi, Claude Code, and OpenCode that guides AI agents through disciplined Test-Driven Development and Clean Code practices. Note that currently this is WORK IN PROGRESS! I'm not even trying to keep it stable or tested.
Other skills on tdder.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about java.
- Which coding agents can use java?
- java installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

