--- name: standards-java description: Java coding standards for enterprise applications. Includes naming conventions, modern Java features, design patterns, and recommended tooling. type: context applies_to: [java, maven, gradle, junit, spring, jakarta, quarkus, mockito, testcontainers, hibernate, jpa] file_extensions: [".java"] --- # 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) ```java // 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) ```java // Define closed set of subclasses public sealed interface Result permits Success, Failure { } public record Success(T value) implements Result {} public record Failure(String error) implements Result {} // Pattern matching exhaustiveness public void handleResult(Result result) { switch (result) { case Success s -> System.out.println("Success: " + s.value()); case Failure f -> System.out.println("Error: " + f.error()); // No default needed - compiler knows all cases } } ``` #### Pattern Matching (instanceof) ```java // 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) ```java // Old way String json = "{\n" + " \"name\": \"John\",\n" + " \"age\": 30\n" + "}"; // Modern way - text block String json = """ { "name": "John", "age": 30 } """; ``` #### Switch Expressions ```java // 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 ```java // 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 user = scope.fork(() -> fetchUser(id)); Future> 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 ```java // New interfaces: SequencedCollection, SequencedSet, SequencedMap // Get first and last elements List list = List.of("a", "b", "c"); String first = list.getFirst(); // "a" String last = list.getLast(); // "c" // Reversed view (not a copy!) List reversed = list.reversed(); // Works with Set LinkedHashSet 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 map = new LinkedHashMap<>(); map.putFirst("first", 1); map.putLast("last", 99); ``` #### Pattern Matching for switch (finalized) ```java // 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) ```java 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 ```java // 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) ```java // ThreadLocal (old way) - must be cleaned up manually private static final ThreadLocal CURRENT_USER = new ThreadLocal<>(); // Scoped Values (new way) - automatically cleaned up public static final ScopedValue 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) ```java // 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) ```java // 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 ```java // 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** ```java // 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** ```java // 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 ```java // 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 ```java // 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 ```java // 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 ```java // List - ordered, allows duplicates List names = new ArrayList<>(); // Set - no duplicates Set uniqueNames = new HashSet<>(); // Map - key-value pairs Map userById = new HashMap<>(); // Prefer List.of(), Set.of(), Map.of() for immutable collections List immutableList = List.of("a", "b", "c"); Set immutableSet = Set.of(1, 2, 3); Map immutableMap = Map.of("a", 1, "b", 2); ``` ### Stream Best Practices ```java // Filter and map List activeUserNames = users.stream() .filter(User::isActive) .map(User::name) .toList(); // Java 16+, or .collect(Collectors.toList()) // Find first Optional firstAdmin = users.stream() .filter(User::isAdmin) .findFirst(); // Reduce int totalAge = users.stream() .mapToInt(User::age) .sum(); // Group by Map> 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 ```java // GOOD - Optional for return values (absence is expected) public Optional findUserById(String id) { return repository.findById(id); } // BAD - don't use Optional for parameters or fields public void processUser(Optional user) { /* avoid */ } private Optional currentUser; /* avoid */ // GOOD - use null for parameters if optional public void processUser(User user) { if (user != null) { // process } } ``` ### Optional Best Practices ```java // 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 maybeUser = findUser(id); User user = maybeUser.get(); // NoSuchElementException if empty // GOOD User user = maybeUser.orElseThrow(); // More explicit ``` ### Null Safety ```java // 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 ```java 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 ```java 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 ```java // Descriptive names - what scenario, what expected @Test void shouldReturnEmptyWhenUserNotFound() { } @Test void shouldThrowExceptionWhenEmailIsInvalid() { } @Test void shouldCalculateDiscountWhenUserIsVip() { } ``` ## Build Tool Awareness ### Maven Dependencies (pom.xml) ```xml org.springframework.boot spring-boot-starter-web org.junit.jupiter junit-jupiter test ``` ### Gradle Dependencies (build.gradle.kts) ```kotlin 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 ```java // 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 ```java // 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