skills/standards-java/SKILL.md
Java coding standards for enterprise applications. Includes naming conventions, modern Java features, design patterns, and recommended tooling.
npx skillsauth add b33eep/claude-code-setup standards-javaInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| 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 |
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
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
Recommended: Use the latest LTS for new projects (currently Java 21 or Java 25).
// 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
// 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
}
}
// 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;
};
}
// Old way
String json = "{\n" +
" \"name\": \"John\",\n" +
" \"age\": 30\n" +
"}";
// Modern way - text block
String json = """
{
"name": "John",
"age": 30
}
""";
// 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";
};
// 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());
}
// 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 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 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";
};
}
// 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");
}
}
// 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);
});
});
}
// 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);
}
// 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);
// 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));
}
}
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;
}
}
// 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);
}
}
// 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
}
// 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);
}
}
// 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);
// 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();
// 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
}
}
// 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
// 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
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));
}
}
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());
}
}
// Descriptive names - what scenario, what expected
@Test
void shouldReturnEmptyWhenUserNotFound() { }
@Test
void shouldThrowExceptionWhenEmailIsInvalid() { }
@Test
void shouldCalculateDiscountWhenUserIsVip() { }
<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>
dependencies {
// Core dependencies
implementation("org.springframework.boot:spring-boot-starter-web")
// Test dependencies
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core")
}
// 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/
| 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 |
// 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) { }
development
Guide users through creating, reviewing, and fixing custom skills for Claude — both command skills (invoked via /slash) and context skills (auto-loaded by tech stack). Use when the user asks to create a skill, build a skill, make a new slash command skill, add a coding standards skill, review an existing skill, update a skill, or fix a skill that doesn't trigger.
development
Build or edit Slidev (sli.dev) presentations for tech talks, workshops, conference sessions, and live-coding demos. Use when the user asks to create slides, a deck, a presentation, a workshop deck, a conference talk, or edit an existing slides.md.
documentation
Download YouTube video transcripts with automatic frame extraction for visual references. Use when analyzing YouTube videos, tutorials, or conference talks.
documentation
Write INVEST-compliant user stories with Given-When-Then acceptance criteria. Use when writing user stories, creating acceptance criteria, or during /design Step 4.