skills/gabia/agent-skills/java-coding-guideline

java-coding-guideline

SKILL.md

Java Coding Guideline

CRITICAL: These rules apply to ALL Java code unless explicitly overridden by more specific skills.

Import Statements

Always use explicit imports without wildcards for better code clarity and maintainability.

✅ Do

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

❌ Don't

import static org.assertj.core.api.Assertions.*;

Vertical Spacing (Readability)

Use consistent vertical spacing inside classes to make diffs smaller and code easier to scan.

Rule

  • Insert exactly one blank line between:
    • field blocks and the next field block (when grouped)
    • fields and the first constructor
    • constructors and methods
    • methods and the next method
  • Also insert one blank line right after the opening { of a class (before the first field/initializer), unless the class is intentionally empty.

✅ Do

public final class SomeClass {

    @NonNull
    private final String fieldA;

    @NonNull
    private final String fieldB;

    public void someFunc() {
        // some action
    }

    public String getFieldA() {
        return fieldA;
    }
}

❌ Don't

public final class SomeClass {
    @NonNull
    private final String fieldA;
    @NonNull
    private final String fieldB;
    public void someFunc() {
        // some action
    }
    public String getFieldA() {
        return fieldA;
    }
}

Constructors

Use a single primary constructor and delegate auxiliary constructors using this().

✅ Do

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;
    }
}

❌ Don't

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;
    }
}

Optional Usage

Avoid using Optional as return values or parameters. Use @Nullable annotations instead.

✅ Do

public final class SomeClass {

    @Nullable
    public Integer someNullable() {
        if (something) return null;
        return 1;
    }

    public void func(@Nullable Integer param) {
        // do something
    }
}

❌ Don't

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
    }
}

Wrapper vs Primitive Types

  • Use wrapper types when null is expected
  • Use primitive types when null should not occur

✅ Do

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;
    }
}

❌ Don't

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;
    }
}

Null Comparison

  • Use A == null for general cases
  • Use Objects.isNull for Stream API or functional programming contexts

✅ Do

final Integer someVariable = null;

if (someVariable == null) {
    // do something
}

List<Integer> lists = new ArrayList<>();
lists.stream()
  .filter(Objects::isNull)
  .collect(Collectors.toList());

❌ Don't

final Integer someVariable = null;

if (Objects.isNull(someVariable)) {
    // do something
}

List<Integer> lists = new ArrayList<>();
lists.stream()
  .filter(p -> p == null)
  .collect(Collectors.toList());

Nullability Annotations

  • Use JSpecify annotations
  • Apply @Nullable and @NonNull to all methods including private ones
  • Avoid class-level or package-level annotations (Lombok compatibility issues)

✅ Do

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";
    }
}

❌ Don't

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 Naming

Use all lowercase and numbers only, following Google Java Style Guide conventions.

✅ Do

package com.example.project.initscript;

❌ Don't

package com.example.project.initScript;

Static Factory Methods

Use only of naming convention. For multiple types, use format of<Postfix>.

✅ Do

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);
    }
}

❌ Don't

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);
    }
}

Resource Management

Always use try-with-resources for AutoCloseable resources. Never rely on finalizers.

Try-With-Resources

✅ Do

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);
    }
}

❌ Don't

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
    }
}

Composition over Inheritance

Prefer composition over inheritance.

✅ Do

public final class SomeFeature {
    public void func() { /* ... */ }
}

public final class SomeClass {

    private final SomeFeature feature;

    public void func2() {
        feature.func();
        // do something
    }
}

❌ Don't

public class SomeFeature {
    public void func() { /* ... */ }
}

public final class SomeClass extends SomeFeature {

    public void func2() {
        super.func();
        // do something
    }
}

Immutable Objects

All objects should use final keyword unless needed for extension or framework requirements.

✅ Do

public final class SomeClass {
    // ...
}

❌ Don't

public class SomeClass {
    // ...
}

Exception Catching

Catch only the minimum necessary exceptions.

✅ Do

try {
    // do something
} catch (IllegalArgumentException e) {
    // do catch
}

❌ Don't

try {
    // do something
} catch (RuntimeException e) {
    // do catch
}

Implementing AutoCloseable

When implementing AutoCloseable:

  1. Make close() idempotent (safe to call multiple times)
  2. Never throw exceptions from close() (suppressed exceptions problem)
  3. Log errors instead of throwing them

✅ Do

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);
        }
    }
}

❌ Don't

public final class DatabaseConnection implements AutoCloseable {

    @NonNull
    private final Connection connection;

    @Override
    public void close() throws SQLException {
        connection.close();  // Not idempotent; exception propagates
    }
}

Date/Time API

Use java.time (JSR-310) exclusively. java.util.Date and java.util.Calendar are banned in new code.

Type Selection

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

✅ Do

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);
    }
}

Formatter reuse (thread-safe):

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() {}
}

❌ Don't

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);
    }
}

Legacy Interop (boundary only)

When interfacing with legacy APIs that require Date, convert at the boundary:

// Instant → Date (boundary layer only)
Date legacyDate = Date.from(instant);

// Date → Instant (convert immediately on receipt)
Instant instant = legacyDate.toInstant();

Additional Guidelines

Builder Pattern

Use Lombok-style builders for API consistency.

Variable and Method Naming

  • Use var keyword from JVM 10+
  • Use meaningful variable names
    • But avoid too long variable, function, field names
  • Method names should be verbs
  • Avoid using 'get' prefix except for getters

Camel Case Convention

Apply camel case to mixed-case words and abbreviations (e.g., "MultiNIC" becomes "MultiNic").

Control Flow Statements

Use one-liner syntax for simple if statements without braces.

✅ Do

if (someCondition) doSomething();

❌ Don't

if (someCondition) {
    doSomething();
}

Comments

  • Use comments only in special cases
  • TODO comments should be for immediate tasks
  • Avoid unnecessary comments that restate the code

See Also

Reference Guides

For deeper understanding of specific topics:

Weekly Installs
8
GitHub Stars
3
First Seen
Feb 4, 2026
Installed on
opencode8
codex5
gemini-cli4
github-copilot4
kimi-cli4
amp4