--- name: java-developer description: Guide for developing on the Java 21 baseline, including modern features, best practices, and integration with Groovy/Grails projects license: Apache-2.0 --- ## What I Do - Provide guidance on Java 21 syntax, features, and APIs for use in Grails/Groovy projects. - Assist with code generation, refactoring, and debugging using modern Java features like records, sealed classes, pattern matching, and text blocks. - Recommend best practices for Java code that interoperates with Groovy in mixed-language projects. - Guide on tooling: Gradle builds, JDK setup, testing with JUnit 5/Spock, and profiling. ## When to Use Me Use this skill when working on Java code within this repository, especially for: - Writing Java classes that will be used alongside Groovy code. - Implementing features using records, sealed classes, or pattern matching for instanceof. - Migrating older Java code (e.g., Java 8/11/17) to Java 21 idioms. - Performance optimization, security hardening, or module system (JPMS) questions. - Understanding how Java code integrates with Groovy's dynamic features. ## Java 21 Baseline Features ### Records (JEP 395) Immutable data carriers with auto-generated constructors, accessors, equals, hashCode, and toString: ```java public record Book(String title, String author, int year) { // Compact constructor for validation public Book { if (title == null || title.isBlank()) { throw new IllegalArgumentException("Title cannot be blank"); } } } ``` ### Sealed Classes (JEP 409) Restrict which classes can extend or implement a type: ```java public sealed interface Shape permits Circle, Rectangle, Triangle { double area(); } public final class Circle implements Shape { private final double radius; public Circle(double radius) { this.radius = radius; } public double area() { return Math.PI * radius * radius; } } public non-sealed class Rectangle implements Shape { // Can be further extended } ``` ### Pattern Matching for instanceof (JEP 394) Eliminate redundant casts: ```java // Before pattern matching if (obj instanceof String) { String s = (String) obj; System.out.println(s.length()); } // Modern Java if (obj instanceof String s) { System.out.println(s.length()); } ``` ### Text Blocks (JEP 378) Multi-line string literals with proper formatting: ```java String json = """ { "name": "Grails", "version": "7.0" } """; String sql = """ SELECT id, name, created_at FROM users WHERE status = 'ACTIVE' ORDER BY created_at DESC """; ``` ### Switch Expressions (JEP 361) Use switch as an expression with arrow syntax: ```java String result = switch (day) { case MONDAY, FRIDAY, SUNDAY -> "Relaxed"; case TUESDAY -> "Productive"; case THURSDAY, SATURDAY -> "Moderate"; case WEDNESDAY -> { var temp = calculateWorkload(); yield temp > 5 ? "Busy" : "Normal"; } }; ``` ### Enhanced NullPointerException Messages (JEP 358) Detailed NPE messages showing exactly which variable was null: ``` Cannot invoke "String.length()" because "user.getAddress().getCity()" is null ``` ### Helpful Stream and Collection APIs ```java // Stream.toList() - unmodifiable list List names = users.stream() .map(User::getName) .toList(); // Collectors improvements Map> byStatus = users.stream() .collect(Collectors.groupingBy(User::getStatus)); ``` ## Java/Groovy Interoperability ### Calling Java from Groovy Groovy seamlessly calls Java code: ```groovy // Java record used in Groovy def book = new Book("Grails Guide", "Author", 2024) println book.title() // Groovy can use property syntax too println book.title // Also works ``` ### Calling Groovy from Java ```java // Groovy classes are just Java classes GroovyService service = new GroovyService(); service.process(data); // Working with Groovy closures in Java Closure closure = ...; String result = closure.call("input"); ``` ### Best Practices for Mixed Projects - Use Java for performance-critical code with `@CompileStatic` equivalent behavior. - Use Java records for DTOs shared between Java and Groovy code. - Prefer Java interfaces that Groovy classes implement. - Avoid Groovy-specific features (like categories) in APIs consumed by Java. ## Build and Testing ### Gradle Configuration ```groovy java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' options.compilerArgs += ['-parameters'] // Preserve parameter names } ``` ### Testing with JUnit 5 ```java @Test @DisplayName("Book record should validate title") void bookShouldValidateTitle() { assertThrows(IllegalArgumentException.class, () -> new Book("", "Author", 2024) ); } @ParameterizedTest @ValueSource(strings = {"", " ", " "}) void blankTitlesShouldBeRejected(String title) { assertThrows(IllegalArgumentException.class, () -> new Book(title, "Author", 2024) ); } ``` ### Testing with Spock (from Groovy) ```groovy def "Java record should work in Spock tests"() { when: def book = new Book("Test", "Author", 2024) then: book.title() == "Test" book.author() == "Author" } ``` ## Performance and Profiling ### JVM Options for Java 21 ```bash # Recommended GC for most workloads -XX:+UseG1GC # For low-latency requirements -XX:+UseZGC # Memory settings -Xms512m -Xmx2g # Enable JFR for profiling -XX:StartFlightRecording=duration=60s,filename=recording.jfr ``` ### Profiling Tools - **JDK Flight Recorder (JFR)**: Built-in low-overhead profiler - **VisualVM**: GUI-based monitoring and profiling - **async-profiler**: Low-overhead sampling profiler ## Common Patterns ### Null Handling with Optional ```java public Optional findById(Long id) { return Optional.ofNullable(repository.get(id)); } // Usage String name = findById(id) .map(User::getName) .orElse("Unknown"); ``` ### Resource Management with try-with-resources ```java try (var reader = new BufferedReader(new FileReader(path)); var writer = new BufferedWriter(new FileWriter(output))) { reader.lines() .map(String::toUpperCase) .forEach(line -> writer.write(line + "\n")); } ``` ### Immutable Collections ```java // Create immutable collections List list = List.of("a", "b", "c"); Set set = Set.of("x", "y", "z"); Map map = Map.of("one", 1, "two", 2); // Copy to immutable List copy = List.copyOf(mutableList); ``` ## Code Style Guidelines - Follow existing patterns in the codebase. - Use `var` for local variables when the type is obvious from context. - Prefer records for simple data carriers. - Use sealed classes to model restricted type hierarchies. - Add `@Override` annotation when overriding methods. - Use meaningful parameter names (preserved with `-parameters` flag). ## Resources - **Java 21 Documentation**: https://docs.oracle.com/en/java/javase/21/ - **Java Language Updates**: https://docs.oracle.com/en/java/javase/21/language/ - **JEP Index**: https://openjdk.org/jeps/0