|
| 1 | +<!-- |
| 2 | +SPDX-License-Identifier: Apache-2.0 |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + https://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +--> |
| 16 | +--- |
| 17 | +name: java-developer |
| 18 | +description: Guide for developing in Java 17 LTS, including modern features, best practices, and integration with Groovy/Grails projects |
| 19 | +license: Apache-2.0 |
| 20 | +compatibility: opencode, claude, grok, gemini, copilot, cursor, windsurf |
| 21 | +metadata: |
| 22 | + audience: developers |
| 23 | + languages: java |
| 24 | + versions: 17 |
| 25 | +--- |
| 26 | + |
| 27 | +## What I Do |
| 28 | + |
| 29 | +- Provide guidance on Java 17 LTS syntax, features, and APIs for use in Grails/Groovy projects. |
| 30 | +- Assist with code generation, refactoring, and debugging using Java 17 enhancements like records, sealed classes, pattern matching, and text blocks. |
| 31 | +- Recommend best practices for Java code that interoperates with Groovy in mixed-language projects. |
| 32 | +- Guide on tooling: Gradle builds, JDK setup, testing with JUnit 5/Spock, and profiling. |
| 33 | + |
| 34 | +## When to Use Me |
| 35 | + |
| 36 | +Use this skill when working on Java code within this repository, especially for: |
| 37 | +- Writing Java classes that will be used alongside Groovy code. |
| 38 | +- Implementing features using records, sealed classes, or pattern matching for instanceof. |
| 39 | +- Migrating older Java code (e.g., Java 8/11) to Java 17 idioms. |
| 40 | +- Performance optimization, security hardening, or module system (JPMS) questions. |
| 41 | +- Understanding how Java code integrates with Groovy's dynamic features. |
| 42 | + |
| 43 | +## Java 17 Key Features |
| 44 | + |
| 45 | +### Records (JEP 395) |
| 46 | +Immutable data carriers with auto-generated constructors, accessors, equals, hashCode, and toString: |
| 47 | +```java |
| 48 | +public record Book(String title, String author, int year) { |
| 49 | + // Compact constructor for validation |
| 50 | + public Book { |
| 51 | + if (title == null || title.isBlank()) { |
| 52 | + throw new IllegalArgumentException("Title cannot be blank"); |
| 53 | + } |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +### Sealed Classes (JEP 409) |
| 59 | +Restrict which classes can extend or implement a type: |
| 60 | +```java |
| 61 | +public sealed interface Shape permits Circle, Rectangle, Triangle { |
| 62 | + double area(); |
| 63 | +} |
| 64 | + |
| 65 | +public final class Circle implements Shape { |
| 66 | + private final double radius; |
| 67 | + public Circle(double radius) { this.radius = radius; } |
| 68 | + public double area() { return Math.PI * radius * radius; } |
| 69 | +} |
| 70 | + |
| 71 | +public non-sealed class Rectangle implements Shape { |
| 72 | + // Can be further extended |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +### Pattern Matching for instanceof (JEP 394) |
| 77 | +Eliminate redundant casts: |
| 78 | +```java |
| 79 | +// Before Java 17 |
| 80 | +if (obj instanceof String) { |
| 81 | + String s = (String) obj; |
| 82 | + System.out.println(s.length()); |
| 83 | +} |
| 84 | + |
| 85 | +// Java 17 |
| 86 | +if (obj instanceof String s) { |
| 87 | + System.out.println(s.length()); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +### Text Blocks (JEP 378) |
| 92 | +Multi-line string literals with proper formatting: |
| 93 | +```java |
| 94 | +String json = """ |
| 95 | + { |
| 96 | + "name": "Grails", |
| 97 | + "version": "7.0" |
| 98 | + } |
| 99 | + """; |
| 100 | + |
| 101 | +String sql = """ |
| 102 | + SELECT id, name, created_at |
| 103 | + FROM users |
| 104 | + WHERE status = 'ACTIVE' |
| 105 | + ORDER BY created_at DESC |
| 106 | + """; |
| 107 | +``` |
| 108 | + |
| 109 | +### Switch Expressions (JEP 361) |
| 110 | +Use switch as an expression with arrow syntax: |
| 111 | +```java |
| 112 | +String result = switch (day) { |
| 113 | + case MONDAY, FRIDAY, SUNDAY -> "Relaxed"; |
| 114 | + case TUESDAY -> "Productive"; |
| 115 | + case THURSDAY, SATURDAY -> "Moderate"; |
| 116 | + case WEDNESDAY -> { |
| 117 | + var temp = calculateWorkload(); |
| 118 | + yield temp > 5 ? "Busy" : "Normal"; |
| 119 | + } |
| 120 | +}; |
| 121 | +``` |
| 122 | + |
| 123 | +### Enhanced NullPointerException Messages (JEP 358) |
| 124 | +Detailed NPE messages showing exactly which variable was null: |
| 125 | +``` |
| 126 | +Cannot invoke "String.length()" because "user.getAddress().getCity()" is null |
| 127 | +``` |
| 128 | + |
| 129 | +### Helpful Stream and Collection APIs |
| 130 | +```java |
| 131 | +// Stream.toList() - unmodifiable list |
| 132 | +List<String> names = users.stream() |
| 133 | + .map(User::getName) |
| 134 | + .toList(); |
| 135 | + |
| 136 | +// Collectors improvements |
| 137 | +Map<Status, List<User>> byStatus = users.stream() |
| 138 | + .collect(Collectors.groupingBy(User::getStatus)); |
| 139 | +``` |
| 140 | + |
| 141 | +## Java/Groovy Interoperability |
| 142 | + |
| 143 | +### Calling Java from Groovy |
| 144 | +Groovy seamlessly calls Java code: |
| 145 | +```groovy |
| 146 | +// Java record used in Groovy |
| 147 | +def book = new Book("Grails Guide", "Author", 2024) |
| 148 | +println book.title() // Groovy can use property syntax too |
| 149 | +println book.title // Also works |
| 150 | +``` |
| 151 | + |
| 152 | +### Calling Groovy from Java |
| 153 | +```java |
| 154 | +// Groovy classes are just Java classes |
| 155 | +GroovyService service = new GroovyService(); |
| 156 | +service.process(data); |
| 157 | + |
| 158 | +// Working with Groovy closures in Java |
| 159 | +Closure<String> closure = ...; |
| 160 | +String result = closure.call("input"); |
| 161 | +``` |
| 162 | + |
| 163 | +### Best Practices for Mixed Projects |
| 164 | +- Use Java for performance-critical code with `@CompileStatic` equivalent behavior. |
| 165 | +- Use Java records for DTOs shared between Java and Groovy code. |
| 166 | +- Prefer Java interfaces that Groovy classes implement. |
| 167 | +- Avoid Groovy-specific features (like categories) in APIs consumed by Java. |
| 168 | + |
| 169 | +## Build and Testing |
| 170 | + |
| 171 | +### Gradle Configuration |
| 172 | +```groovy |
| 173 | +java { |
| 174 | + toolchain { |
| 175 | + languageVersion = JavaLanguageVersion.of(17) |
| 176 | + } |
| 177 | +} |
| 178 | +
|
| 179 | +tasks.withType(JavaCompile).configureEach { |
| 180 | + options.encoding = 'UTF-8' |
| 181 | + options.compilerArgs += ['-parameters'] // Preserve parameter names |
| 182 | +} |
| 183 | +``` |
| 184 | + |
| 185 | +### Testing with JUnit 5 |
| 186 | +```java |
| 187 | +@Test |
| 188 | +@DisplayName("Book record should validate title") |
| 189 | +void bookShouldValidateTitle() { |
| 190 | + assertThrows(IllegalArgumentException.class, () -> |
| 191 | + new Book("", "Author", 2024) |
| 192 | + ); |
| 193 | +} |
| 194 | + |
| 195 | +@ParameterizedTest |
| 196 | +@ValueSource(strings = {"", " ", " "}) |
| 197 | +void blankTitlesShouldBeRejected(String title) { |
| 198 | + assertThrows(IllegalArgumentException.class, () -> |
| 199 | + new Book(title, "Author", 2024) |
| 200 | + ); |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +### Testing with Spock (from Groovy) |
| 205 | +```groovy |
| 206 | +def "Java record should work in Spock tests"() { |
| 207 | + when: |
| 208 | + def book = new Book("Test", "Author", 2024) |
| 209 | +
|
| 210 | + then: |
| 211 | + book.title() == "Test" |
| 212 | + book.author() == "Author" |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +## Performance and Profiling |
| 217 | + |
| 218 | +### JVM Options for Java 17 |
| 219 | +```bash |
| 220 | +# Recommended GC for most workloads |
| 221 | +-XX:+UseG1GC |
| 222 | + |
| 223 | +# For low-latency requirements |
| 224 | +-XX:+UseZGC |
| 225 | + |
| 226 | +# Memory settings |
| 227 | +-Xms512m -Xmx2g |
| 228 | + |
| 229 | +# Enable JFR for profiling |
| 230 | +-XX:StartFlightRecording=duration=60s,filename=recording.jfr |
| 231 | +``` |
| 232 | + |
| 233 | +### Profiling Tools |
| 234 | +- **JDK Flight Recorder (JFR)**: Built-in low-overhead profiler |
| 235 | +- **VisualVM**: GUI-based monitoring and profiling |
| 236 | +- **async-profiler**: Low-overhead sampling profiler |
| 237 | + |
| 238 | +## Common Patterns |
| 239 | + |
| 240 | +### Null Handling with Optional |
| 241 | +```java |
| 242 | +public Optional<User> findById(Long id) { |
| 243 | + return Optional.ofNullable(repository.get(id)); |
| 244 | +} |
| 245 | + |
| 246 | +// Usage |
| 247 | +String name = findById(id) |
| 248 | + .map(User::getName) |
| 249 | + .orElse("Unknown"); |
| 250 | +``` |
| 251 | + |
| 252 | +### Resource Management with try-with-resources |
| 253 | +```java |
| 254 | +try (var reader = new BufferedReader(new FileReader(path)); |
| 255 | + var writer = new BufferedWriter(new FileWriter(output))) { |
| 256 | + reader.lines() |
| 257 | + .map(String::toUpperCase) |
| 258 | + .forEach(line -> writer.write(line + "\n")); |
| 259 | +} |
| 260 | +``` |
| 261 | + |
| 262 | +### Immutable Collections |
| 263 | +```java |
| 264 | +// Create immutable collections |
| 265 | +List<String> list = List.of("a", "b", "c"); |
| 266 | +Set<String> set = Set.of("x", "y", "z"); |
| 267 | +Map<String, Integer> map = Map.of("one", 1, "two", 2); |
| 268 | + |
| 269 | +// Copy to immutable |
| 270 | +List<String> copy = List.copyOf(mutableList); |
| 271 | +``` |
| 272 | + |
| 273 | +## Code Style Guidelines |
| 274 | + |
| 275 | +- Follow existing patterns in the codebase. |
| 276 | +- Use `var` for local variables when the type is obvious from context. |
| 277 | +- Prefer records for simple data carriers. |
| 278 | +- Use sealed classes to model restricted type hierarchies. |
| 279 | +- Add `@Override` annotation when overriding methods. |
| 280 | +- Use meaningful parameter names (preserved with `-parameters` flag). |
| 281 | + |
| 282 | +## Resources |
| 283 | + |
| 284 | +- **Java 17 Documentation**: https://docs.oracle.com/en/java/javase/17/ |
| 285 | +- **Java Language Updates**: https://docs.oracle.com/en/java/javase/17/language/ |
| 286 | +- **JEP Index**: https://openjdk.org/jeps/0 |
0 commit comments