npx skills add ...
npx skills add gabia/agent-skills --skill java-coding-guideline
Use when writing, reviewing, or refactoring Java code to enforce baseline conventions for imports, nullability, immutability, exceptions, resource handling, naming, and concurrency.
npx skills add gabia/agent-skills --skill java-coding-guideline
CRITICAL: These rules apply to ALL Java code unless explicitly overridden by more specific skills.
Always use explicit imports without wildcards for better code clarity and maintainability.
Use consistent vertical spacing inside classes to make diffs smaller and code easier to scan.
{ of a class (before the first field/initializer), unless the class is intentionally empty.Use a single primary constructor and delegate auxiliary constructors using this().
Avoid using Optional as return values or parameters. Use @Nullable annotations instead.
null is expectednull should not occurA == null for general casesObjects.isNull for Stream API or functional programming contexts@Nullable and @NonNull to all methods including private onesUse all lowercase and numbers only, following Google Java Style Guide conventions.
Use only of naming convention. For multiple types, use format of<Postfix>.
Always use try-with-resources for AutoCloseable resources. Never rely on finalizers.
Prefer composition over inheritance.
All objects should use final keyword unless needed for extension or framework requirements.
Choose between an assertion and an exception based on the trust boundary and the caller's recovery responsibility. Apply this rule to each condition, not to an entire class or method.
Do not infer the boundary from a Java access modifier or a component name. A public method may be an internal API, while a private parser may process external input.
| Condition | Mechanism |
|---|---|
| Invalid external request, command, message, or public API argument | Throw an exception |
| Invalid runtime configuration | Throw an exception |
| Network, database, filesystem, or external service failure | Throw an exception |
| Security, authorization, or data integrity check | Throw an exception |
| Caller must translate, retry, trace, log, or otherwise handle the failure | Throw an exception |
| Programmer-controlled precondition or postcondition in an internal API | Use assert |
| Relationship guaranteed by internal wiring or dispatch | Use assert |
| State that is unreachable when the implementation is correct | Use assert |
Throw an exception when a failure can occur during valid runtime operation or the caller must be able to respond to it. External boundaries include:
Use an exception even when assertions are enabled. Externally visible behavior must not depend on the JVM -ea option.
Choose an exception type that communicates the failure:
IllegalArgumentException for an invalid caller-supplied argumentIllegalStateException when the current runtime state cannot satisfy the operationUncheckedIOException when intentionally exposing an I/O failure as an unchecked exceptionDocument externally visible exceptions with @throws.
Use assert for programmer-controlled preconditions, postconditions, and invariants in internal APIs. This includes internal publishers, generators, converters, and parsers that consume trusted application data.
Typical assertion cases include:
Assertions may be disabled in production. Therefore:
AssertionError as normal control flowAdd an assertion message when the invariant is not obvious.
The same method may contain both assertions and exceptions.
The pool relationship is an internal routing invariant. The requested size is a runtime argument that a caller may legitimately provide incorrectly.
An internal API must still throw exceptions for operational failures such as an S3 upload failure, database error, or network timeout. Internal API status alone does not turn an expected runtime failure into an assertion.
Validate external data at the boundary with an exception. After converting it into trusted internal data, use assertions to document the propagated invariant.
A parser follows the same rule:
Throwing an exception does not mean that every layer should log it. Preserve the original cause and propagate the exception until a layer can handle, translate, retry, or terminate the operation. Log the exception once at that handling boundary instead of logging and rethrowing it at every layer.
Catch only the minimum necessary exceptions.
When implementing AutoCloseable:
close() idempotent (safe to call multiple times)close() (suppressed exceptions problem)Use java.time (JSR-310) exclusively. java.util.Date and java.util.Calendar are banned in new code.
| Use Case | Type | Example |
|---|---|---|
| Storage/transfer (UTC) | Instant | API timestamps, DB storage |
| User display | ZonedDateTime | Time shown in UI |
| Date only | LocalDate | Birthdays, expiration dates |
| Time only | LocalTime | Business hours, alarms |
| Duration | Duration / Period | Elapsed time, date differences |
Formatter reuse (thread-safe):
When interfacing with legacy APIs that require Date, convert at the boundary:
Use Lombok-style builders for API consistency.
var keyword from JVM 10+Apply camel case to mixed-case words and abbreviations (e.g., "MultiNIC" becomes "MultiNic").
Use one-liner syntax for simple if statements without braces.
For deeper understanding of specific topics:
public final class SomeClass {
@NonNull
private final String fieldA;
@NonNull
private final String fieldB;
public void someFunc() {
// some action
}
public String getFieldA() {
return fieldA;
}
}public final class SomeClass {
@NonNull
private final String fieldA;
@NonNull
private final String fieldB;
public void someFunc() {
// some action
}
public String getFieldA() {
return fieldA;
}
}public final class SomeClass {
private final int field1;
private final int field2;
public SomeClass() {
this(1, 2);
}
public SomeClass(int field1, int field2) {
this.field1 = field1;
this.field2 = field2;
}
}public final class SomeClass {
private final int field1;
private final int field2;
public SomeClass() {
this.field1 = 1;
this.field2 = 2;
}
public SomeClass(int field1, int field2) {
this.field1 = field1;
this.field2 = field2;
}
}public final class SomeClass {
@Nullable
public Integer someNullable() {
if (something) return null;
return 1;
}
public void func(@Nullable Integer param) {
// do something
}
}public final class SomeClass {
@NonNull
public Optional<Integer> someNullable() {
if (something) return Optional.ofNullable(null);
return Optional.ofNullable(1);
}
public void func(@NonNull Optional<Integer> param) {
// do something
}
}public final class SomeClass {
private final int field1;
@Nullable
private final Long field2;
public SomeClass(int field1, @Nullable Long field2) {
this.field1 = field1;
this.field2 = field2;
}
}public final class SomeClass {
@NonNull
private final Integer field1;
@Nullable
private final Long field2;
public SomeClass(@NonNull Integer field1, @Nullable Long field2) {
this.field1 = field1;
this.field2 = field2;
}
}final Integer someVariable = null;
if (someVariable == null) {
// do something
}
List<Integer> lists = new ArrayList<>();
lists.stream()
.filter(Objects::isNull)
.collect(Collectors.toList());final Integer someVariable = null;
if (Objects.isNull(someVariable)) {
// do something
}
List<Integer> lists = new ArrayList<>();
lists.stream()
.filter(p -> p == null)
.collect(Collectors.toList());public final class SomeClass {
@Nullable
private final Integer field1;
@NonNull
private final String field2;
@NonNull
public String func() {
return process();
}
@Nullable
private String process() {
if (field1 == null) return null;
return "awesome";
}
}public final class SomeClass {
private final Integer field1;
private final String field2;
public String func() {
return process();
}
private String process() {
if (field1 == null) return null;
return "awesome";
}
}package com.example.project.initscript;package com.example.project.initScript;public final class SomeClass {
private final int field1;
private SomeClass(int field1) {
this.field1 = field1;
}
public static SomeClass ofDefault() {
return new SomeClass(1);
}
public static SomeClass ofSpecial() {
return new SomeClass(2);
}
}public final class SomeClass {
private final int field1;
private SomeClass(int field1) {
this.field1 = field1;
}
public static SomeClass from() {
return new SomeClass(1);
}
public static SomeClass valueOf() {
return new SomeClass(2);
}
}public String readFile(@NonNull Path path) throws IOException {
try (var reader = Files.newBufferedReader(path)) {
return reader.lines().collect(Collectors.joining("\n"));
}
}
// Multiple resources
public void copyStream(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
try (in; out) { // Java 9+: can use already-declared variables
in.transferTo(out);
}
}public String readFile(@NonNull Path path) throws IOException {
BufferedReader reader = Files.newBufferedReader(path);
try {
return reader.lines().collect(Collectors.joining("\n"));
} finally {
reader.close(); // The original exception may be suppressed if one occurs here
}
}public final class SomeFeature {
public void func() { /* ... */ }
}
public final class SomeClass {
private final SomeFeature feature;
public void func2() {
feature.func();
// do something
}
}public class SomeFeature {
public void func() { /* ... */ }
}
public final class SomeClass extends SomeFeature {
public void func2() {
super.func();
// do something
}
}public final class SomeClass {
// ...
}public class SomeClass {
// ...
}public void publish(@NonNull String objectKey, @NonNull String generatedScript) {
assert !objectKey.isBlank();
assert !generatedScript.isBlank();
objectStore.put(objectKey, generatedScript);
}assert collector.getAgentId() == agent.getId() : "collector must own the agent";public void resize(@NonNull Pool pool, @NonNull Bucket bucket, long requestedSize, long minimumSize) {
assert pool.getId() == bucket.getPoolId();
assert pool.getType() == PoolType.OBJECT_STORAGE;
if (requestedSize < minimumSize) {
throw new IllegalArgumentException("requested size is smaller than the minimum size");
}
}try {
// do something
} catch (IllegalArgumentException e) {
// do catch
}try {
// do something
} catch (RuntimeException e) {
// do catch
}public final class DatabaseConnection implements AutoCloseable {
@NonNull
private final Connection connection;
private volatile boolean closed = false;
@Override
public void close() {
if (closed) return; // Idempotent: safe to call multiple times
closed = true;
try {
connection.close();
} catch (SQLException e) {
// Log only; do not throw (exceptions in close can suppress the original)
logger.warn("Failed to close connection", e);
}
}
}public final class DatabaseConnection implements AutoCloseable {
@NonNull
private final Connection connection;
@Override
public void close() throws SQLException {
connection.close(); // Not idempotent; exception propagates
}
}public final class Event {
@NonNull
private final Instant createdAt; // UTC timestamp
@NonNull
private final LocalDate eventDate; // Date only
@NonNull
private final ZoneId timeZone; // Display time zone
@NonNull
public ZonedDateTime displayTime() {
return createdAt.atZone(timeZone);
}
}public final class DateFormats {
// DateTimeFormatter is thread-safe, so reuse as constants
public static final DateTimeFormatter ISO_DATE = DateTimeFormatter.ISO_LOCAL_DATE;
public static final DateTimeFormatter ENGLISH_DATE = DateTimeFormatter.ofPattern("MMM dd, yyyy");
private DateFormats() {}
}public final class Event {
@NonNull
private final Date createdAt; // Do not use java.util.Date
@NonNull
private final Calendar calendar; // Do not use java.util.Calendar
@NonNull
public String format() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // Not thread-safe
return sdf.format(createdAt);
}
}// Instant → Date (boundary layer only)
Date legacyDate = Date.from(instant);
// Date → Instant (convert immediately on receipt)
Instant instant = legacyDate.toInstant();if (someCondition) doSomething();if (someCondition) {
doSomething();
}