Skip to main content

loom-java

Java language expertise for idiomatic, production-quality code.

Quellinformationen

Repository
cosmix/loom
Letzte Quellaktivität
24. September 2026 um 17:08
Erkannte Sprache von SKILL.md
Englisch
Sterne
61
Forks
4

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
loom-java
description
Java language expertise for idiomatic, production-quality code.
triggers
["java","jdk","jvm","javac","maven","mvn","pom.xml","surefire","gradle","gradlew","junit","jupiter","assertj","mockito","testcontainers","spring","spring boot","jpa","hibernate","jakarta","record","sealed","virtual threads","completablefuture","optional"]
# Java Language Expertise ## Overview Idiomatic, production-grade Java 21+ (25 is the current LTS): records, sealed hierarchies, pattern matching, streams, virtual threads, the Maven and Gradle builds, JUnit 5 and Spring Boot. Assumes the reader knows the language. The content is decision rules and the traps that survive review: `==` on boxed values, `Collectors.toMap` on a duplicate key, `@Transactional` on a self-call, a pinned virtual thread, a test task that runs zero JUnit 5 tests and reports success. ## Tooling Use the wrapper the repository ships (`./gradlew`, `./mvnw`); it pins the build tool version. Keep the existing build tool: a Maven-to-Gradle migration is a separate, requested change. | Task | Gradle | Maven | | --- | --- | --- | | Full gate | `./gradlew check` | `./mvnw -q verify` | | Compile main and tests | `./gradlew testClasses` | `./mvnw -q test-compile` | | Unit tests | `./gradlew test` | `./mvnw -q test` | | One test | `./gradlew test --tests 'com.acme.FooTest.bar'` | `./mvnw test -Dtest='FooTest#bar'` | | Dependency tree | `./gradlew dependencies --configuration runtimeClasspath` | `./mvnw dependency:tree` | | Why is X on the classpath | `./gradlew dependencyInsight --dependency jackson-databind` | `./mvnw dependency:tree -Dincludes=com.fasterxml.jackson.core` | | Format | `./gradlew spotlessApply` | `./mvnw spotless:apply` | - **Gradle:** `check` runs `test` plus every verification task a plugin attaches (Spotless's `spotlessCheck`, Checkstyle, SpotBugs); Error Prone runs inside `compileJava`. `build` is `check` plus `assemble`. A `test` task whose inputs have not changed is skipped as `UP-TO-DATE`; `--rerun` (Gradle 7.6+) forces it. - **Maven:** Surefire runs unit tests in the `test` phase (default includes `**/Test*.java`, `**/*Test.java`, `**/*Tests.java`, `**/*TestCase.java`); Failsafe runs `*IT.java` in `integration-test`/`verify`. Pin `maven-surefire-plugin` 3.x in `<pluginManagement>`: older Maven super-POMs bind Surefire 2.12.4, which runs no JUnit 5 tests and reports success. - **Versions:** Gradle keeps them in the version catalog (`gradle/libs.versions.toml`) and imports BOMs with `platform(...)`; Maven imports BOMs in `<dependencyManagement>` with `<scope>import</scope>`. Conflict resolution differs: Gradle picks the highest requested version, Maven picks the nearest declaration in the tree, which can silently downgrade a transitive dependency. `maven-enforcer-plugin`'s `dependencyConvergence` rule turns that into a build error. - **Static analysis:** `javac -Xlint:all -Werror`; Error Prone (javac plugin, compile-time bug patterns); NullAway (an Error Prone check) with JSpecify `@NullMarked`/`@Nullable`; SpotBugs on bytecode; Checkstyle or PMD for style rules. Format with Spotless running google-java-format or palantir-java-format. ```kotlin // build.gradle.kts for a Java project plugins { java alias(libs.plugins.spotless) alias(libs.plugins.errorprone) } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } dependencies { testImplementation(platform(libs.junit.bom)) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") testImplementation(libs.assertj.core) errorprone(libs.errorprone.core) } tasks.withType<JavaCompile>().configureEach { options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) } tasks.test { useJUnitPlatform() // without it Gradle runs the JUnit 4 engine, which does not see Jupiter tests } ``` ## Records, Sealed Types and Pattern Matching Model data as records and closed sets of alternatives as sealed interfaces; `switch` then checks exhaustiveness (Java 21). ```java public sealed interface Command { record Upload(Path source, String target) implements Command { public Upload { // compact constructor: runs on every construction Objects.requireNonNull(source, "source"); if (target == null || target.isBlank()) throw new IllegalArgumentException("target is blank"); } } record Delete(String target) implements Command {} record Batch(List<Command> commands) implements Command { public Batch { commands = List.copyOf(commands); } // defensive copy; rejects null elements } } static String describe(Command command) { return switch (command) { // exhaustive over the sealed type: no default case Command.Upload(Path source, var target) -> "upload " + source + " -> " + target; case Command.Delete(String target) -> "delete " + target; case Command.Batch(var commands) when commands.isEmpty() -> "empty batch"; case Command.Batch(var commands) -> "batch of " + commands.size(); }; } ``` - Records nested in the sealed interface need no `permits` clause. Jackson (2.12+) deserializes records through the canonical constructor, so compact-constructor validation also guards parsed input. - Records are shallowly immutable. Copy mutable components (`List.copyOf`, `Map.copyOf`) in the compact constructor. An array component keeps reference equality: `record Blob(byte[] data)` has an `equals` that compares array identity; override `equals`/`hashCode` with `Arrays.equals`/`Arrays.hashCode` or hold an immutable type. - Leave `default` out of a `switch` over a sealed type. A new subtype then fails compilation at every switch that must handle it; a `default` branch absorbs it silently. - `instanceof` patterns bind and narrow in one step: `if (obj instanceof String s && !s.isBlank())`. The unnamed pattern `_` (Java 22) ignores record components you do not use. - Enums for fixed sets without per-instance data; `EnumMap`/`EnumSet` are array-backed and faster than hash collections keyed by enums. - `var` for locals whose type the right-hand side states (`var users = new ArrayList<User>()`); spell the type when the initializer is a call whose return type the reader cannot see. ## Errors and Null Handling ```java public Config load(Path path) throws ConfigException { try (var reader = Files.newBufferedReader(path)) { // closed on every exit path return parse(reader); } catch (NoSuchFileException e) { // specific before general throw new ConfigException("config not found: " + path, e); } catch (IOException e) { throw new ConfigException("reading config " + path, e); // always pass the cause } } ``` - Checked exceptions for conditions a caller can act on (missing file, refused connection); unchecked (`IllegalArgumentException`, `IllegalStateException`) for bugs and violated preconditions. Checked exceptions do not pass through lambdas: wrap in `UncheckedIOException` inside a stream and unwrap at the boundary. - Pass the cause to every wrapping exception. `throw new X(msg)` inside a `catch` discards the original stack trace. - try-with-resources closes resources in reverse order; an exception thrown by `close()` is attached to the primary one as suppressed (`getSuppressed()`). - `InterruptedException`: restore the flag with `Thread.currentThread().interrupt()` before returning or rethrowing unchecked. Swallowing it breaks cancellation for every caller up the stack. - `Optional` is a return type. Keep it out of fields, parameters and collections. `orElse(compute())` always evaluates `compute()`; `orElseGet(this::compute)` is lazy. Prefer `orElseThrow()` to `get()`. - Null: `Objects.requireNonNull(x, "x")` at public boundaries; JSpecify `@NullMarked` on the package plus `@Nullable` on the exceptions, checked at compile time by NullAway. Return empty collections instead of `null`. ## Streams and Collections ```java Map<String, Integer> totals = orders.stream() .collect(Collectors.toMap(Order::customer, Order::amount, Integer::sum)); // merge: duplicates add up List<String> names = users.stream() .filter(User::active) .map(User::name) .toList(); // unmodifiable (Java 16+) Map<Status, List<Order>> byStatus = orders.stream() .collect(Collectors.groupingBy(Order::status, () -> new EnumMap<>(Status.class), Collectors.toList())); ``` - `Collectors.toMap` without a merge function throws `IllegalStateException` on a duplicate key, and `NullPointerException` on a null value. - `Stream.toList()` returns an unmodifiable list that allows nulls; `Collectors.toList()` makes no promise about mutability; `Collectors.toUnmodifiableList()` rejects nulls. - `List.of`, `Set.of`, `Map.of` are immutable and reject nulls; `Set.of`/`Map.of` throw on duplicates at construction, and their iteration order is unspecified and changes between JVM runs. Sort, or use `LinkedHashMap`/`TreeMap`, wherever order is observable (JSON output, snapshots, log lines under test). - `Arrays.asList` is fixed-size and writes through to the backing array. - Sequenced collections (21): `getFirst()`, `getLast()`, `reversed()` on lists, deques, `LinkedHashSet` and `LinkedHashMap`. - Streams are lazy and single-use. Side effects in `peek` or `map` may never run: since Java 9, `count()` skips the pipeline when the size is known from the source. - `parallelStream()` runs on the JVM-wide common `ForkJoinPool`. Blocking IO inside it starves every other parallel stream and every `CompletableFuture.supplyAsync` without an executor. Use it only for CPU-bound work on large inputs, measured. - Stream gatherers (final in 24): `stream.gather(Gatherers.windowFixed(3))`, `windowSliding`, and `Gatherers.mapConcurrent(n, fn)` for bounded concurrent mapping on virtual threads. ## Concurrency ```java try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { List<Future<String>> pages = urls.stream() .map(url -> executor.submit(() -> fetch(url))) // one virtual thread per blocking call .toList(); for (Future<String> page : pages) { consume(page.get()); // ExecutionException wraps the task's failure } } // close() waits for every submitted task ``` - **Virtual threads (21)** are for blocking IO at high concurrency. Create one per task and never pool them. The executor has no size limit, so bound calls to a downstream service with a `Semaphore`. - **Pinning:** before JDK 24, blocking inside `synchronized` pinned the carrier thread; JEP 491 (JDK 24) removed that for `synchronized`. Native frames (JNI, some drivers) still pin. On 21, guard blocking calls with `ReentrantLock` and watch the `jdk.VirtualThreadPinned` JFR event. - `ThreadLocal` works on virtual threads, but each thread gets its own copy: a per-thread cache of heavy objects multiplies by the thread count. `ScopedValue` (final in 25) passes request context down a call tree immutably. - CPU-bound work belongs on a platform pool sized to the cores (`Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())`) or a `ForkJoinPool`. - `CompletableFuture.supplyAsync(fn)` without an executor runs on the common pool; pass one. `join()` throws unchecked `CompletionException`, `get()` throws checked `ExecutionException`. Attach `exceptionally`/`handle` at the end of a chain; a failed future nobody observes fails silently. - `StructuredTaskScope` is still a preview API in 25 and its shape changed between releases; keep it out of code that must build without `--enable-preview`. - Shared state: `ConcurrentHashMap.merge`/`compute` for atomic read-modify-write (a `get` followed by `put` races); `LongAdder` for contended counters, `AtomicLong` otherwise; `volatile` for a flag with one writer. Immutable records cross threads without locks. - Spring Boot 3.2+: `spring.threads.virtual.enabled=true` serves requests on virtual threads. ## Packages and Modules - Standard layout: `src/main/java`, `src/main/resources`, `src/test/java`, `src/test/resources`; the directory path matches the package. - Package-private (no modifier) is the default for implementation classes; mark `public` only the API. Tests in the same package reach package-private members without widening them. - JPMS (`module-info.java`): `exports` the API packages, `requires transitive` for types your API exposes, `opens` for packages that reflective frameworks (Jackson, Hibernate, Spring) must read. A missing `opens` surfaces at runtime as `InaccessibleObjectException`. Applications that publish no library usually stay on the classpath. ## Spring Boot Essentials ```java @ConfigurationProperties(prefix = "spool") @Validated public record SpoolProperties(@NotBlank String directory, @Positive int maxFiles) {} @Service class SpoolService { private final SpoolRepository repository; SpoolService(SpoolRepository repository) { // single constructor: injected without @Autowired this.repository = repository; } @Transactional public void archive(long id) { var entry = repository.findById(id).orElseThrow(() -> new EntryNotFoundException(id)); entry.markArchived(); // dirty checking writes the change at commit } } ``` - Register properties with `@ConfigurationPropertiesScan` or `@EnableConfigurationProperties(SpoolProperties.class)`. Records bind through the constructor; `@Validated` fails startup on invalid values. - Proxy-based annotations (`@Transactional`, `@Async`, `@Cacheable`) do nothing on a self-call: `this.archive(id)` from the same bean bypasses the proxy. Move the method to another bean. - Rollback happens by default only for unchecked exceptions and `Error`; a checked exception commits. Use `@Transactional(rollbackFor = Exception.class)` where checked exceptions signal failure. - Set `spring.jpa.open-in-view=false`. The default keeps the persistence context open through response rendering, hides lazy-loading N+1 queries and holds a connection for the whole request (Boot logs a warning about it). Load what a use case needs with `JOIN FETCH` or `@EntityGraph`. - JPA entities are classes with a no-arg constructor. Records cannot be entities. Base `equals`/`hashCode` on the id or a business key, and keep Lombok `@Data` off entities: its `toString`/`hashCode` walk lazy associations. - Actuator: expose `health` and `info`; keep `env`, `heapdump`, `loggers` and the rest behind authentication. - Boot 3+ uses the `jakarta.*` namespace (`jakarta.persistence`, `jakarta.validation`). `javax.*` imports mark Boot 2 era code. ## Testing ```java class SpoolWriterTest { @TempDir Path root; @Test void writesEntryUnderRoot() throws IOException { new SpoolWriter(root).write("a.json", "{}"); assertThat(root.resolve("a.json")).hasContent("{}"); } @ParameterizedTest @ValueSource(strings = {"../escape.json", "/etc/passwd", "a/../../b"}) void rejectsPathsOutsideRoot(String name) { var writer = new SpoolWriter(root); assertThatThrownBy(() -> writer.write(name, "{}")) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("outside spool root"); } } ``` - JUnit Jupiter (JUnit 5; JUnit 6 keeps the `org.junit.jupiter.api` annotations): `@Test`, `@ParameterizedTest` with `@ValueSource`/`@CsvSource`/`@MethodSource`, `@TempDir`, `@Nested`, `@BeforeEach`. Test classes and methods can be package-private. - AssertJ (`assertThat(...)`, `assertThatThrownBy`) produces better failure messages than `assertEquals`. JUnit's `assertThrows` returns the exception for further checks. - Mockito with `@ExtendWith(MockitoExtension.class)` uses strict stubs: an unused stub fails the test. Mock roles you own; build value objects for real. - Integration tests: Testcontainers with the production database engine. H2 in a compatibility mode differs in SQL dialect and locking. - Spring slices (`@WebMvcTest`, `@DataJpaTest`, `@JsonTest`) load a narrow context; `@SpringBootTest` loads all of it. Each distinct context configuration (another set of mocked beans, other properties) starts a new context, the usual cause of slow Spring suites. `@MockitoBean` (Spring Framework 6.2) replaces the deprecated `@MockBean`.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen