standards-java

SKILL.md

Java Coding Standards

Core Principles

  1. Simplicity: Simple, understandable code
  2. Readability: Readability over cleverness
  3. Maintainability: Code that's easy to maintain
  4. Testability: Code that's easy to test
  5. SOLID: Follow SOLID principles for object-oriented design
  6. DRY: Don't Repeat Yourself - but don't overdo it

General Rules

  • Early Returns: Use early returns to avoid nesting
  • Descriptive Names: Meaningful names for classes, methods, and variables
  • Minimal Changes: Only change relevant code parts
  • No Over-Engineering: No unnecessary complexity
  • Immutability: Prefer immutable objects where possible
  • Minimal Comments: Code should be self-explanatory. No redundant comments!

Naming Conventions

Element Convention Example
Classes PascalCase UserService, OrderRepository
Interfaces PascalCase UserRepository, PaymentProcessor
Methods camelCase getUserById, calculateTotal
Variables camelCase firstName, totalAmount
Constants UPPER_SNAKE_CASE MAX_RETRY_COUNT, DEFAULT_TIMEOUT
Packages lowercase.dot.separated com.example.service, com.example.repository
Test Classes ClassNameTest UserServiceTest, OrderRepositoryTest
Test Methods descriptive_snake_case or camelCase shouldReturnUserWhenIdExists

Project Structure

Maven Project

myproject/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/myapp/
│   │   │       ├── Application.java        # Main entry point
│   │   │       ├── config/
│   │   │       │   └── AppConfig.java      # Configuration
│   │   │       ├── domain/
│   │   │       │   └── User.java           # Domain models
│   │   │       ├── repository/
│   │   │       │   └── UserRepository.java # Data access
│   │   │       ├── service/
│   │   │       │   └── UserService.java    # Business logic
│   │   │       └── controller/
│   │   │           └── UserController.java # REST endpoints
│   │   └── resources/
│   │       ├── application.properties
│   │       └── application-dev.properties
│   └── test/
│       ├── java/
│       │   └── com/example/myapp/
│       │       ├── service/
│       │       │   └── UserServiceTest.java
│       │       └── repository/
│       │           └── UserRepositoryTest.java
│       └── resources/
│           └── application-test.properties
└── README.md

Gradle Project

myproject/
├── build.gradle or build.gradle.kts
├── settings.gradle or settings.gradle.kts
├── src/
│   ├── main/
│   │   └── java/...      # Same structure as Maven
│   └── test/
│       └── java/...      # Same structure as Maven
└── README.md

Modern Java Features

Recommended: Use the latest LTS for new projects (currently Java 21 or Java 25).

Java 17 Features

Records (Immutable Data)

// Replace verbose POJOs with records
public record User(String id, String name, String email) {
    // Compact constructor for validation
    public User {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be blank");
        }
    }

    // Custom methods allowed
    public String displayName() {
        return name.toUpperCase();
    }
}

// Usage
var user = new User("1", "John Doe", "john@example.com");
System.out.println(user.name()); // Auto-generated accessor

Sealed Classes (Restricted Hierarchies)

// Define closed set of subclasses
public sealed interface Result<T>
    permits Success, Failure {
}

public record Success<T>(T value) implements Result<T> {}
public record Failure<T>(String error) implements Result<T> {}

// Pattern matching exhaustiveness
public <T> void handleResult(Result<T> result) {
    switch (result) {
        case Success<T> s -> System.out.println("Success: " + s.value());
        case Failure<T> f -> System.out.println("Error: " + f.error());
        // No default needed - compiler knows all cases
    }
}

Pattern Matching (instanceof)

// Old way
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}

// Modern way - pattern matching
if (obj instanceof String s) {
    System.out.println(s.toUpperCase());
}

// Pattern matching in switch
public String formatValue(Object obj) {
    return switch (obj) {
        case Integer i -> "Number: " + i;
        case String s -> "Text: " + s;
        case null -> "null";
        default -> "Unknown: " + obj;
    };
}

Text Blocks (Multi-line Strings)

// Old way
String json = "{\n" +
              "  \"name\": \"John\",\n" +
              "  \"age\": 30\n" +
              "}";

// Modern way - text block
String json = """
    {
      "name": "John",
      "age": 30
    }
    """;

Switch Expressions

// Old switch statement
String result;
switch (day) {
    case MONDAY:
    case FRIDAY:
        result = "Work";
        break;
    case SATURDAY:
    case SUNDAY:
        result = "Weekend";
        break;
    default:
        result = "Unknown";
}

// Modern switch expression
String result = switch (day) {
    case MONDAY, FRIDAY -> "Work";
    case SATURDAY, SUNDAY -> "Weekend";
    default -> "Unknown";
};

Java 21 Features

Virtual Threads

// Traditional platform threads - expensive, limited scalability
try (var executor = Executors.newFixedThreadPool(100)) {
    for (int i = 0; i < 10000; i++) {
        executor.submit(() -> fetchData());
    }
}

// Virtual threads - lightweight, millions possible
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10000; i++) {
        executor.submit(() -> fetchData());
    }
}

// Start virtual thread directly
Thread.startVirtualThread(() -> {
    // Task code
});

// Structured concurrency (preview in Java 21)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<String> user = scope.fork(() -> fetchUser(id));
    Future<List<Order>> orders = scope.fork(() -> fetchOrders(id));

    scope.join();           // Wait for all tasks
    scope.throwIfFailed();  // Throw if any failed

    return new UserDetails(user.resultNow(), orders.resultNow());
}

Sequenced Collections

// New interfaces: SequencedCollection, SequencedSet, SequencedMap

// Get first and last elements
List<String> list = List.of("a", "b", "c");
String first = list.getFirst();  // "a"
String last = list.getLast();    // "c"

// Reversed view (not a copy!)
List<String> reversed = list.reversed();

// Works with Set
LinkedHashSet<String> set = new LinkedHashSet<>(List.of("a", "b", "c"));
set.addFirst("z");  // z, a, b, c
set.addLast("x");   // z, a, b, c, x

// Works with Map
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.putFirst("first", 1);
map.putLast("last", 99);

Pattern Matching for switch (finalized)

// Pattern matching with null handling
String formatted = switch (obj) {
    case null -> "null";
    case Integer i -> "Number: " + i;
    case String s -> "Text: " + s;
    case List<?> list -> "List of " + list.size() + " items";
    default -> "Unknown";
};

// Guard patterns
String category = switch (value) {
    case Integer i when i < 0 -> "Negative";
    case Integer i when i == 0 -> "Zero";
    case Integer i -> "Positive";
    default -> "Not a number";
};

Record Patterns (finalized)

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

// Deconstruct records in patterns
static void printPoint(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println("x: " + x + ", y: " + y);
    }
}

// Nested deconstruction
static void printCircle(Object obj) {
    if (obj instanceof Circle(Point(int x, int y), int r)) {
        System.out.println("Circle at (" + x + ", " + y + ") with radius " + r);
    }
}

// In switch
static String describe(Object obj) {
    return switch (obj) {
        case Point(int x, int y) -> "Point at (" + x + ", " + y + ")";
        case Circle(Point(int x, int y), int r) ->
            "Circle at (" + x + ", " + y + ") radius " + r;
        default -> "Unknown shape";
    };
}

Java 25 Features

Flexible Main Methods

// No longer need public static void main(String[] args)

// Simple main - for beginners and scripts
void main() {
    System.out.println("Hello World");
}

// With arguments (if needed)
void main(String[] args) {
    System.out.println("Args: " + Arrays.toString(args));
}

// Instance main (access to instance fields/methods)
class App {
    private String message = "Hello";

    void main() {
        System.out.println(message);  // Access instance field
        greet();                       // Call instance method
    }

    void greet() {
        System.out.println("Welcome");
    }
}

Scoped Values (Alternative to ThreadLocal)

// ThreadLocal (old way) - must be cleaned up manually
private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>();

// Scoped Values (new way) - automatically cleaned up
public static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();

// Set scoped value (automatically reverted when block exits)
ScopedValue.runWhere(CURRENT_USER, user, () -> {
    // Value is available here
    User currentUser = CURRENT_USER.get();
    processRequest(currentUser);
    // Value automatically cleared when block exits
});

// Inherited by virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    ScopedValue.runWhere(CURRENT_USER, user, () -> {
        executor.submit(() -> {
            // Virtual thread inherits scoped value
            User u = CURRENT_USER.get();
            handleTask(u);
        });
    });
}

Primitive Pattern Matching (Preview)

// Pattern matching now works with primitives

static String classify(int value) {
    return switch (value) {
        case 0 -> "zero";
        case int i when i > 0 -> "positive";
        case int i when i < 0 -> "negative";
    };
}

// Type patterns for primitives
Object obj = 42;
if (obj instanceof int i) {
    System.out.println("Integer: " + i);
}

Gatherers (Custom Stream Operations)

// Create custom intermediate stream operations

// Built-in gatherers
Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowFixed(2))  // [[1,2], [3,4], [5]]
    .toList();

Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowSliding(2))  // [[1,2], [2,3], [3,4], [4,5]]
    .toList();

// Custom gatherer example (simplified)
var sumAndCount = Gatherer.of(
    () -> new long[2],  // [sum, count]
    (state, element, downstream) -> {
        state[0] += element;
        state[1]++;
        return true;
    },
    (state, downstream) -> {
        downstream.push(state[0] / (double) state[1]);
    }
);

double average = Stream.of(1, 2, 3, 4, 5)
    .gather(sumAndCount)
    .findFirst()
    .orElse(0.0);

Code Organization

Visibility Modifiers

// Use the most restrictive visibility possible
public class UserService {
    private final UserRepository repository;  // private - internal state

    public UserService(UserRepository repository) {  // public - API
        this.repository = repository;
    }

    public User findById(String id) {  // public - API
        return validateAndFetch(id);
    }

    private User validateAndFetch(String id) {  // private - internal logic
        if (id == null || id.isBlank()) {
            throw new IllegalArgumentException("ID cannot be blank");
        }
        return repository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

SOLID Principles

Single Responsibility Principle

// BAD - class does too much
public class UserService {
    public void createUser(User user) { /* ... */ }
    public void sendEmail(String to, String message) { /* ... */ }
    public void logActivity(String activity) { /* ... */ }
}

// GOOD - single responsibility
public class UserService {
    private final UserRepository repository;
    private final EmailService emailService;
    private final AuditService auditService;

    public void createUser(User user) {
        repository.save(user);
        emailService.sendWelcomeEmail(user);
        auditService.logUserCreation(user.id());
    }
}

Dependency Inversion

// GOOD - depend on abstractions, not implementations
public class UserService {
    private final UserRepository repository;  // interface, not concrete class

    public UserService(UserRepository repository) {
        this.repository = repository;
    }
}

Exception Handling

Checked vs Unchecked

// Checked exceptions for recoverable errors (use sparingly)
public class UserNotFoundException extends Exception {
    public UserNotFoundException(String userId) {
        super("User not found: " + userId);
    }
}

// Unchecked exceptions for programming errors (preferred)
public class InvalidUserIdException extends RuntimeException {
    public InvalidUserIdException(String userId) {
        super("Invalid user ID: " + userId);
    }
}

Try-with-Resources

// Automatic resource management
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
    // reader automatically closed
} catch (IOException e) {
    throw new UncheckedIOException(e);
}

// Multiple resources
try (var inputStream = new FileInputStream("in.txt");
     var outputStream = new FileOutputStream("out.txt")) {
    // Both automatically closed in reverse order
}

Custom Exception Hierarchies

// Base exception for domain
public class DomainException extends RuntimeException {
    public DomainException(String message) {
        super(message);
    }
}

// Specific exceptions
public class UserNotFoundException extends DomainException {
    public UserNotFoundException(String userId) {
        super("User not found: " + userId);
    }
}

public class InvalidEmailException extends DomainException {
    public InvalidEmailException(String email) {
        super("Invalid email: " + email);
    }
}

Collections & Streams API

When to Use Which Collection

// List - ordered, allows duplicates
List<String> names = new ArrayList<>();

// Set - no duplicates
Set<String> uniqueNames = new HashSet<>();

// Map - key-value pairs
Map<String, User> userById = new HashMap<>();

// Prefer List.of(), Set.of(), Map.of() for immutable collections
List<String> immutableList = List.of("a", "b", "c");
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);

Stream Best Practices

// Filter and map
List<String> activeUserNames = users.stream()
    .filter(User::isActive)
    .map(User::name)
    .toList();  // Java 16+, or .collect(Collectors.toList())

// Find first
Optional<User> firstAdmin = users.stream()
    .filter(User::isAdmin)
    .findFirst();

// Reduce
int totalAge = users.stream()
    .mapToInt(User::age)
    .sum();

// Group by
Map<String, List<User>> usersByRole = users.stream()
    .collect(Collectors.groupingBy(User::role));

// Don't reuse streams (they're one-time use)
// BAD
var stream = users.stream();
stream.filter(...).toList();
stream.map(...).toList();  // IllegalStateException

// GOOD
users.stream().filter(...).toList();
users.stream().map(...).toList();

Optional & Null Handling

When to Use Optional

// GOOD - Optional for return values (absence is expected)
public Optional<User> findUserById(String id) {
    return repository.findById(id);
}

// BAD - don't use Optional for parameters or fields
public void processUser(Optional<User> user) { /* avoid */ }
private Optional<User> currentUser;  /* avoid */

// GOOD - use null for parameters if optional
public void processUser(User user) {
    if (user != null) {
        // process
    }
}

Optional Best Practices

// Chain operations
String userName = userService.findUserById(id)
    .map(User::name)
    .map(String::toUpperCase)
    .orElse("UNKNOWN");

// Throw exception if absent
User user = userService.findUserById(id)
    .orElseThrow(() -> new UserNotFoundException(id));

// Execute action if present
userService.findUserById(id)
    .ifPresent(user -> emailService.sendWelcome(user));

// Don't use Optional.get() without checking
// BAD
Optional<User> maybeUser = findUser(id);
User user = maybeUser.get();  // NoSuchElementException if empty

// GOOD
User user = maybeUser.orElseThrow();  // More explicit

Null Safety

// Use Objects.requireNonNull for validation
public User(String id, String name) {
    this.id = Objects.requireNonNull(id, "id cannot be null");
    this.name = Objects.requireNonNull(name, "name cannot be null");
}

// Prefer Objects utilities
String result = Objects.requireNonNullElse(value, "default");
boolean equals = Objects.equals(a, b);  // null-safe equals

Testing Fundamentals

JUnit 5 Basics

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class UserServiceTest {
    private UserService service;
    private UserRepository repository;

    @BeforeEach
    void setUp() {
        repository = new InMemoryUserRepository();
        service = new UserService(repository);
    }

    @Test
    void shouldReturnUserWhenIdExists() {
        // Given
        var user = new User("1", "John", "john@example.com");
        repository.save(user);

        // When
        var result = service.findById("1");

        // Then
        assertTrue(result.isPresent());
        assertEquals("John", result.get().name());
    }

    @Test
    void shouldThrowWhenIdIsNull() {
        assertThrows(IllegalArgumentException.class,
            () -> service.findById(null));
    }

    @ParameterizedTest
    @ValueSource(strings = {"", "  ", "\t"})
    void shouldThrowWhenIdIsBlank(String id) {
        assertThrows(IllegalArgumentException.class,
            () -> service.findById(id));
    }
}

Mockito for Mocking

import org.mockito.*;
import static org.mockito.Mockito.*;

class UserServiceTest {
    @Mock
    private UserRepository repository;

    @InjectMocks
    private UserService service;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void shouldCallRepositoryWhenFindingUser() {
        // Given
        var user = new User("1", "John", "john@example.com");
        when(repository.findById("1")).thenReturn(Optional.of(user));

        // When
        var result = service.findById("1");

        // Then
        verify(repository).findById("1");
        assertTrue(result.isPresent());
        assertEquals("John", result.get().name());
    }
}

Test Naming

// Descriptive names - what scenario, what expected
@Test
void shouldReturnEmptyWhenUserNotFound() { }

@Test
void shouldThrowExceptionWhenEmailIsInvalid() { }

@Test
void shouldCalculateDiscountWhenUserIsVip() { }

Build Tool Awareness

Maven Dependencies (pom.xml)

<dependencies>
    <!-- Core dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Test dependencies -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle Dependencies (build.gradle.kts)

dependencies {
    // Core dependencies
    implementation("org.springframework.boot:spring-boot-starter-web")

    // Test dependencies
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.mockito:mockito-core")
}

Project Conventions

// Maven standard directory layout
src/main/java       - Production code
src/main/resources  - Configuration files
src/test/java       - Test code
src/test/resources  - Test configuration

// Gradle uses same layout
// Package structure matches directory structure
// com.example.myapp.service -> src/main/java/com/example/myapp/service/

Recommended Tooling

Tool Purpose
maven or gradle Build automation
junit-jupiter Testing framework (JUnit 5)
mockito Mocking framework
checkstyle Code style checking
spotbugs Static bug detection
jacoco Code coverage
maven-enforcer Dependency management rules
testcontainers Integration testing with Docker

Production Best Practices

  1. Immutability - Prefer records and final fields, reduces bugs
  2. Dependency Injection - Constructor injection over field injection
  3. Fail Fast - Validate inputs immediately, throw exceptions early
  4. Explicit over Implicit - Clear code over clever code
  5. Resource Management - Always use try-with-resources for I/O
  6. Optional for Return Types - Signal absence without null
  7. Stream API - Use streams for collection operations, but don't overuse
  8. Modern Java - Use records, sealed classes, pattern matching
  9. Minimal Checked Exceptions - Prefer unchecked for most cases
  10. Constructor Validation - Validate in constructor or compact constructor (records)
  11. Descriptive Names - Method names should explain intent
  12. Single Responsibility - Classes and methods should do one thing
  13. Package by Feature - Not by layer (service/repository/controller in same package)
  14. Logging - Use SLF4J, structured logging, appropriate levels
  15. Configuration - Externalize via application.properties, environment variables

Comments - Less is More

// BAD - redundant comment
// Get user from repository
User user = repository.findById(id);

// GOOD - self-explanatory code, no comment needed
User user = repository.findById(id);

// GOOD - comment explains WHY (not obvious)
// Rate limit: API allows max 100 requests per minute per client
rateLimiter.acquire();

// GOOD - comment documents constraint
/**
 * Processes payment. Amount must be positive.
 * @throws IllegalArgumentException if amount <= 0
 */
public void processPayment(BigDecimal amount) { }

References

Weekly Installs
2
GitHub Stars
47
First Seen
Feb 28, 2026
Installed on
mcpjam2
claude-code2
replit2
junie2
windsurf2
zencoder2