Skill: ivangdavila/java (version 1.0.1)

--- name: Java slug: java version: 1.0.1 description: Write robust Java avoiding null traps, equality bugs, and concurrency pitfalls. metadata: {"clawdbot":{"emoji":"☕","requires":{"bins":["java","javac"]},"os":["linux","darwin","win32"]}} --- ## Quick Reference | Topic | File | |-------|------| | Nulls, Optional, autoboxing | `nulls.md` | | Collections and iteration traps | `collections.md` | | Generics and type erasure | `generics.md` | | Concurrency and synchronization | `concurrency.md` | | Classes, inheritance, memory | `classes.md` | | Streams and CompletableFuture | `streams.md` | | Testing (JUnit, Mockito) | `testing.md` | | JVM, GC, modules | `jvm.md` | ## Critical Rules - `==` compares references, not content — always use `.equals()` for strings - Override `equals()` must also override `hashCode()` — HashMap/HashSet break otherwise - `Optional.get()` throws if empty — use `orElse()`, `orElseGet()`, or `ifPresent()` - Modifying while iterating throws `ConcurrentModificationException` — use Iterator.remove() - Type erasure: generic type info gone at runtime — can't do `new T()` or `instanceof List<String>` - `volatile` ensures visibility, not atomicity — `count++` still needs synchronization - Unboxing null throws NPE — `Integer i = null; int x = i;` crashes - `Integer == Integer` uses reference for values outside -128 to 127 — use `.equals()` - Try-with-resources auto-closes — implement `AutoCloseable`, Java 7+ - Inner classes hold reference to outer — use static nested class if not needed - Streams are single-use — can't reuse after terminal operation - `thenApply` vs `thenCompose` — compose for chaining CompletableFutures - Records are implicitly final — can't extend, components are final - `serialVersionUID` mismatch breaks deserialization — always declare explicitly

path: classes.md
content: # Classes, Inheritance & Memory

## Inheritance Quirks
- `private` methods not overridden — new method with same name in child
- `static` methods hide, don't override — called based on reference type, not object
- `super()` must be first statement in constructor — no logic before
- `final` methods can't be overridden — `final` class can't be extended
- Fields don't participate in polymorphism — accessed by reference type
- Constructors not inherited — must define explicitly or get default
- `@Override` annotation catches typos — compiler error if not actually overriding

## Memory Management
- Leaked listeners/callbacks prevent GC — remove references when done
- `WeakReference` for caches — allows GC when memory needed
- `static` collections grow forever — clear or use weak/soft references
- Inner classes hold reference to outer — use static nested class if not needed
- `finalize()` deprecated — use Cleaner or try-with-resources
- `SoftReference` vs `WeakReference` — soft cleared only on memory pressure
- PhantomReference for cleanup actions — use with ReferenceQueue

## Modern Java Features
- Records (16+): immutable data carriers — auto-generates equals, hashCode, toString
- Sealed classes (17+): restrict inheritance — `permits` clause lists allowed subclasses
- Pattern matching in switch (21+): type patterns and guards — cleaner than instanceof chains
- Virtual threads (21+): lightweight concurrency — don't pool, create freely
- `var` for local variables (10+) — inferred type, still strongly typed

## Records & Sealed Gotchas
- Records are implicitly final — can't extend, can implement interfaces
- Record components are final — can't reassign after construction
- Compact constructor for validation — `public Point { if (x < 0) throw ...; }`
- Full constructor needed for transformation — compact can't reassign components
- Records can have static fields and methods — but not instance fields
- Sealed classes: `permits` in same file — or same package if not explicit
- `non-sealed` subclass breaks the seal — any class can extend it
- Pattern matching deconstructs records — `case Point(int x, int y) when x > 0`
- Sealed interfaces work too — same rules apply
- Text blocks (15+): `"""` multiline strings — trailing whitespace stripped


path: collections.md
content: # Collections & Iteration

## Equality Contract
- Override `equals()` must also override `hashCode()` — HashMap/HashSet break otherwise
- `equals()` must be symmetric, transitive, consistent — `a.equals(b)` implies `b.equals(a)`
- Use `getClass()` check, not `instanceof` — unless explicitly designed for inheritance
- `hashCode()` must return same value for equal objects — unequal objects can share hash
- Arrays: `Arrays.equals()` for content — `array.equals(other)` uses reference comparison

## Collections Pitfalls
- Modifying while iterating throws `ConcurrentModificationException` — use Iterator.remove() or copy
- `Arrays.asList()` returns fixed-size list — can't add/remove, backed by array
- `List.of()`, `Set.of()` return immutable — throw on modification attempts
- `HashMap` allows null key and values — `Hashtable` and `ConcurrentHashMap` don't
- Sort requires `Comparable` or `Comparator` — ClassCastException if missing


path: concurrency.md
content: # Concurrency & Synchronization

## Concurrency
- `volatile` ensures visibility, not atomicity — `count++` still needs synchronization
- `synchronized` on method locks `this` — static synchronized locks class object
- Double-checked locking broken without volatile — use holder pattern or enum for singletons
- `ConcurrentHashMap` safe but not atomic for compound ops — use `computeIfAbsent()`
- Thread pool: don't create threads manually — use `ExecutorService`

## Exception Handling
- Checked exceptions must be caught or declared — unchecked (RuntimeException) don't
- Try-with-resources auto-closes — implement `AutoCloseable`, Java 7+
- Catch specific exceptions first — more general catch later or unreachable code error
- Don't catch `Throwable` — includes `Error` which shouldn't be caught
- `finally` always runs — even on return, but return in finally overrides try's return


path: generics.md
content: # Generics & Type Erasure

## Generics Traps
- Type erasure: generic type info gone at runtime — can't do `new T()` or `instanceof List<String>`
- Raw types bypass safety — `List list` allows any type, loses compile-time checks
- `List<Dog>` is not subtype of `List<Animal>` — use wildcards: `List<? extends Animal>`
- `<?>` is not same as `<Object>` — wildcard allows any type, Object only allows Object
- Generic arrays forbidden — `new T[10]` fails, use `ArrayList<T>` instead


path: jvm.md
content: # JVM, GC & Module System

## JVM/GC Pitfalls
- String concatenation uses `invokedynamic` — Java 9+, no StringBuilder needed in simple cases
- Escape analysis may stack-allocate — JIT optimization, don't rely on it for correctness
- G1GC is default since Java 9 — ZGC or Shenandoah for sub-millisecond pauses
- Set `-Xmx` equals `-Xms` in production — avoids heap resize pauses
- GC logs: `-Xlog:gc*` (Java 9+) — essential for troubleshooting
- Metaspace replaces PermGen (Java 8+) — can still OOM with classloader leaks
- `-XX:+UseStringDeduplication` — G1GC only, saves memory on duplicate strings
- Class Data Sharing (CDS) — `-Xshare:dump` and `-Xshare:on` for faster startup
- `jcmd` for runtime diagnostics — thread dumps, heap dumps, GC info
- Native memory: DirectByteBuffer — not in heap, can cause OOM outside -Xmx

## Module System (Java 9+)
- `module-info.java` at root — declares `requires`, `exports`, `opens`
- Split packages forbidden — same package can't exist in two modules
- Reflection needs `opens` — `opens pkg to framework;` for frameworks like Spring
- `requires transitive` exposes — readers of your module get the dependency too
- Automatic modules for classpath JARs — module name from JAR manifest or filename
- `--add-opens` at runtime — escape hatch when you can't modify module-info
- `exports` controls compile-time access — `opens` controls runtime reflection
- Service provider: `provides X with Y` — cleaner than ServiceLoader classpath scanning
- Unnamed module for classpath code — can read all named modules
- `jdeps` analyzes dependencies — use `--jdk-internals` to find illegal access


path: nulls.md
content: # Nulls, Optional & Autoboxing

## String Gotchas
- `==` compares references, not content — always use `.equals()` for strings
- String pool: literals interned, `new String()` not — `"a" == "a"` true, `new String("a") == "a"` false
- Strings are immutable — concatenation in loop creates garbage, use `StringBuilder`
- `null.equals(x)` throws NPE — use `"literal".equals(variable)` or `Objects.equals()`

## Null Handling
- NPE is most common exception — check nulls or use `Optional<T>`
- `Optional.get()` throws if empty — use `orElse()`, `orElseGet()`, or `ifPresent()`
- Don't use Optional for fields or parameters — intended for return types
- `@Nullable` and `@NonNull` annotations help static analysis — not enforced at runtime
- Primitive types can't be null — but wrappers (`Integer`) can, autoboxing hides this

## Autoboxing Dangers
- `Integer == Integer` uses reference for values outside -128 to 127 — use `.equals()`
- Unboxing null throws NPE — `Integer i = null; int x = i;` crashes
- Performance: boxing in tight loops creates garbage — use primitives
- `Integer.valueOf()` caches small values — `new Integer()` never caches (deprecated)


path: streams.md
content: # Streams & CompletableFuture

## Streams Advanced
- Streams are single-use — can't reuse after terminal operation, create new stream
- `findFirst()` vs `findAny()` — findAny may return different element in parallel
- `flatMap()` flattens and maps — `Optional.flatMap()` unwraps nested Optionals
- `peek()` for debugging only — may not execute with short-circuit ops like `findFirst()`
- `toList()` (Java 16+) returns unmodifiable — `Collectors.toList()` is modifiable
- `groupingBy` with downstream — `groupingBy(key, counting())` for aggregation
- Infinite streams need limit — `Stream.iterate()` or `Stream.generate()` runs forever
- `reduce()` identity must be true identity — wrong value breaks parallel streams
- `collect()` vs `reduce()` — collect for mutable containers, reduce for immutable
- Primitive streams: `IntStream`, `LongStream` — avoid boxing overhead

## CompletableFuture Pitfalls
- `thenApply` vs `thenCompose` — compose unwraps nested futures, apply doesn't
- Exception handling: `exceptionally()` recovers, `handle()` transforms both success/failure
- `join()` vs `get()` — join throws unchecked CompletionException, get throws checked
- Default executor is ForkJoinPool.commonPool() — specify custom for blocking I/O
- `allOf()` returns `CompletableFuture<Void>` — must extract results manually
- `anyOf()` returns `CompletableFuture<Object>` — loses type safety
- `orTimeout()` and `completeOnTimeout()` — Java 9+, clean timeout handling
- Async variants: `thenApplyAsync()` — runs on different thread from executor
- `supplyAsync()` for computation — use executor argument for I/O-bound tasks
- Don't block in common pool — starves other parallel operations


path: testing.md
content: # Testing & Serialization

## JUnit 5 Traps
- `@Test` has no expected attribute — use `assertThrows(Exception.class, () -> code)`
- `@BeforeEach` runs per test — `@BeforeAll` once per class (must be static)
- `assertEquals(expected, actual)` — order matters for failure messages
- `assertAll()` for grouped assertions — reports all failures, not just first
- `@Disabled` skips test — not `@Ignore` (that's JUnit 4)
- `@Nested` for test organization — inner class inherits `@BeforeEach`
- `@ParameterizedTest` needs source — `@ValueSource`, `@CsvSource`, `@MethodSource`
- `@TempDir` for temporary files — cleaned up automatically after test

## Mockito Pitfalls
- `when().thenReturn()` for stubbing — must call on mock, not real method
- `verify()` checks interaction — call after the action, not before
- `@Mock` creates fake object — all methods return defaults (null, 0, false)
- `@Spy` wraps real object — real methods called unless stubbed
- `@InjectMocks` does constructor injection — or setter, or field injection
- `ArgumentCaptor` for complex assertions — capture arguments, verify later
- `doReturn().when()` for spies — regular `when()` calls real method first
- `reset()` is code smell — usually means test does too much

## Serialization Gotchas
- `serialVersionUID` must match — mismatch throws InvalidClassException
- `transient` fields not serialized — will be null/default on deserialization
- Custom `writeObject`/`readObject` — must be private, exact signature matters
- Static fields not serialized — belong to class, not instance
- Prefer JSON/Protobuf over Java serialization — security vulnerabilities (CVEs)
- `readResolve()` for singletons — return canonical instance to preserve identity
- Inheritance: parent must be Serializable or have no-arg constructor
- `Externalizable` for full control — must implement `writeExternal`/`readExternal`


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)