Help us improve
Share bugs, ideas, or general feedback.
From tdder
Loads Java and Kotlin coding conventions covering local type inference, static imports, doc comments, exception handling, and API version queries via Adoptium. Useful when writing or modifying JVM source code.
npx claudepluginhub t1/tdder --plugin tdderHow this skill is triggered — by the user, by Claude, or both
Slash command
/tdder:javaThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
JVM language coding conventions complementing the TDD and Clean Code skills.
Enforces Java 17+ coding standards for Spring Boot and Quarkus services: naming, immutability, Optional, streams, exceptions, generics, CDI, reactive patterns, and project layout. Auto-detects framework from build file.
Covers JUnit fundamentals including annotations, assertions, test lifecycle, setup, configuration, and best practices for Java unit testing.
Generates JUnit 5 unit tests with Mockito and Testcontainers integration tests for Java services, repositories, controllers, and utilities. Auto-detects Maven/Gradle/Spring Boot setup from build files.
Share bugs, ideas, or general feedback.
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.
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.
a != b ? doX() : doY()
use a == b ? doY() : doX().@Override void foo() { or @Test void should() {override fun foo() { or @Test fun should() {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
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
IMPORTANT Use Markdown for doc comments to improve in-IDE readability.
Java uses Javadoc (/** */); Kotlin uses KDoc (same /** */ syntax, same Markdown support).
Add comments only if they add real value. Prefer self-explanatory code. Comments should explain "why", not "what".
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.
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).
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:
private final HttpClient httpClient = HttpClient.newHttpClient();val httpClient = HttpClient.newHttpClient()Only well-defined APIs are public. Internals are not, but have limited visibility,
i.e. private if possible. If wider than private is needed:
internal (module-visible)protected is only rarely necessary in either language.
Use BDD-style method names.
Java:
@Test void shouldParseSemanticVersion() { ... }
@Test void shouldReturnEmptyForInvalidInput() { ... }
Kotlin:
@Test fun shouldParseSemanticVersion() { ... }
@Test fun shouldReturnEmptyForInvalidInput() { ... }
For verifications, use assertj-core.
Use then(...) instead of assertThat(...):
then(result).isEqualTo(expected);
then(list).hasSize(3).containsExactly("a","b","c");
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.
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(Item(10), Item(20))
val total = calculator.calculate(items)
then(total).isEqualTo(33.0)
}
Use @Disabled for pending tests in the TDD test list.
Java:
@Disabled("TODO handle the edge case")
@Test void shouldHandleEdgeCase() { ... }
Kotlin:
@Disabled("TODO handle the edge case")
@Test fun shouldHandleEdgeCase() { ... }
Use @Nested classes to group tests by scenario or method-under-test.
Name nested classes descriptively without a Test suffix, e.g. GivenUserIsLoggedIn.
Use @BeforeEach (and maybe a corresponding @AfterEach) within nested classes
when setup is truly shared across all tests in that group.
If the setup is expensive, use @BeforeAll and a static field within the nested class
(and @AfterAll to clean up).
Keep setup close to tests — only extract to @BeforeEach when multiple tests repeat the same given block.
In Kotlin, @Nested test classes must be declared as inner class (JUnit 5 requires non-static nested classes, and
Kotlin nested classes are static by default).
class OrderServiceTest {
OrderService service;
OrderRepository repository = mock();
@BeforeEach void setUp() {
service = new OrderService(repository);
}
@Nested class GivenOrderExists {
Order order = new Order(42, "pending");
@BeforeEach void setUp() {
given(repository.findById(42)).willReturn(Optional.of(order));
}
@Test void shouldReturnOrder() {
var result = service.getOrder(42);
then(result).isEqualTo(order);
}
@Test void shouldCancelOrder() {
service.cancel(42);
then(order.getStatus()).isEqualTo("cancelled");
}
}
@Nested class GivenOrderDoesNotExist {
@BeforeEach void setUp() {
given(repository.findById(42)).willReturn(Optional.empty());
}
@Test void shouldThrow() {
thenThrownBy(() -> service.getOrder(42))
.isInstanceOf(OrderNotFoundException.class);
}
}
}
Neither fakes nor mocks are universally better — choose per dependency.
Fakes are handwritten or simplified implementations (e.g., an in-memory repository backed by a HashMap
or an H2 database).
| Fakes | Mocks | |
|---|---|---|
| Coupling to implementation | Low — tests verify behavior, not calls | Higher — tests verify specific interactions |
| Readability | High — tests read like production code | Moderate — given/verify adds ceremony |
| Maintenance cost | maintain another implementation | repetition in every test |
| Refactoring resilience | High — internals can change freely | Lower — renaming a method breaks stubs |
| Speed of writing | Slower initially | Faster for one-off tests |
Use fakes when the dependency is stable and used across many tests (repositories, clocks, event buses). Use mocks when verifying interactions matters, the dependency is volatile, or a fake would be complex to maintain.
If in doubt, start with a Fake.
System Tests (STs) go in the test.system package under the test root.
Sub-structure this package as the project grows.
Name ST classes with the suffix ST (e.g., BookResourceST.java / BookResourceST.kt). This
naming is not recognized by Surefire's default includes, so STs are
automatically excluded from normal test runs.
The Java compiler outputs one .class file per top-level type plus one per nested/anonymous
type or lambda capture, all derived from a single source file. Build tools normally only add new class files;
they never remove stale ones. So if you move or delete a source file, orphaned .class files remain
on disk and can silently shadow the new code or cause other problems.
This also happens with refactoring tools, i.e. a rename does not clean up old class files.
After deleting, moving, or renaming a source file, immediately remove the matching class files from the build output:
# Example: deleted src/main/java/com/example/Foo.java
find target/classes/com/example \( -name 'Foo.class' -o -name 'Foo$*.class' \) -delete
# Example: deleted src/test/java/com/example/FooTest.java
find target/test-classes/com/example \( -name 'FooTest.class' -o -name 'FooTest$*.class' \) -delete
The glob Foo$*.class covers all nested and anonymous types (Foo$Bar.class, Foo$1.class, …).
Derive the output path from the source path by replacing:
src/main/java → target/classessrc/test/java → target/test-classes(For multi-module projects, prefix with the module directory as usual.)
After editing Java or Kotlin files, always check with the IDE for:
get_file_problems with errorsOnly: false)Java: Use package-info.java files for package-level documentation and architecture.
Kotlin: Use a package.md file (or a dedicated .kt file with only a @file: doc comment and the package
declaration) for the same purpose.
Read these files whenever you read files within a package. They should help agents to work with the files in a package.