Standards java
Persistent memory system for Claude Code via Markdown. Setup with /init-project, resume with /catchup, wrap with /wrapup. Includes coding standards, MCP servers, and modular skills. Solves context loss after /clear.
npx -y skills add b33eep/claude-code-setup --skill standards-javaAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Java coding standards for enterprise applications. Includes naming conventions, modern Java features, design patterns, and recommended tooling.
SKILL.md
22.7 KB, as published. Nobody here has run it
Java Coding Standards
Core Principles
- Simplicity: Simple, understandable code
- Readability: Readability over cleverness
- Maintainability: Code that's easy to maintain
- Testability: Code that's easy to test
- SOLID: Follow SOLID principles for object-oriented design
- 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", "[email protected]");
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", "[email protected]");
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", "[email protected]");
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
- Immutability - Prefer records and final fields, reduces bugs
- Dependency Injection - Constructor injection over field injection
- Fail Fast - Validate inputs immediately, throw exceptions early
- Explicit over Implicit - Clear code over clever code
- Resource Management - Always use try-with-resources for I/O
- Optional for Return Types - Signal absence without null
- Stream API - Use streams for collection operations, but don't overuse
- Modern Java - Use records, sealed classes, pattern matching
- Minimal Checked Exceptions - Prefer unchecked for most cases
- Constructor Validation - Validate in constructor or compact constructor (records)
- Descriptive Names - Method names should explain intent
- Single Responsibility - Classes and methods should do one thing
- Package by Feature - Not by layer (service/repository/controller in same package)
- Logging - Use SLF4J, structured logging, appropriate levels
- 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
- Java Language Specification: https://docs.oracle.com/javase/specs/
- Effective Java by Joshua Bloch
- Clean Code by Robert C. Martin