
Java
- 8 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
java is a Claude skill that helps write robust Java by avoiding null traps, equality bugs, and concurrency pitfalls.
About
This skill helps write robust Java by flagging null traps, equality bugs, and concurrency pitfalls. It provides a critical-rules list plus reference files for nulls and Optional, collections, generics, concurrency, streams, testing, and the JVM. A developer uses it while writing or reviewing Java code.
- Rules for avoiding null, equality, and concurrency bugs in Java
- Reference files for nulls/Optional, collections, generics, concurrency, streams, testing, and the JVM
- Concise critical-rules list of common Java pitfalls
Java by the numbers
- 8 all-time installs (skills.sh)
- Ranked #74 of 89 Java & JVM skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
Java capabilities & compatibility
Free reference; requires a local JDK (java/javac).
- Capabilities
- java review · concurrency review · code review
- Use cases
- code review · testing · debugging
- Platforms
- macOS · Linux · Windows
- Runs
- Runs locally
- Pricing
- Free
What Java says it does
Write robust Java avoiding null traps, equality bugs, and concurrency pitfalls.
`==` compares references, not content
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill javaAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Write and review robust Java code that avoids null, equality, and concurrency pitfalls.
Who is it for?
Developers writing or reviewing Java who want to avoid common correctness pitfalls.
When should I use this skill?
You are writing or reviewing Java and need to avoid null, equality, or concurrency bugs.
By the numbers
- 8 reference topic files
- Integer caching from -128 to 127
Files
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 overridehashCode()— HashMap/HashSet break otherwise Optional.get()throws if empty — useorElse(),orElseGet(), orifPresent()- Modifying while iterating throws
ConcurrentModificationException— use Iterator.remove() - Type erasure: generic type info gone at runtime — can't do
new T()orinstanceof List<String> volatileensures visibility, not atomicity —count++still needs synchronization- Unboxing null throws NPE —
Integer i = null; int x = i;crashes Integer == Integeruses 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
thenApplyvsthenCompose— compose for chaining CompletableFutures- Records are implicitly final — can't extend, components are final
serialVersionUIDmismatch breaks deserialization — always declare explicitly
{
"ownerId": "kn73vp5rarc3b14rc7wjcw8f8580t5d1",
"slug": "java",
"version": "1.0.1",
"publishedAt": 1771100274767
}{
"slug": "java",
"name": "Java",
"version": "1.0.1",
"installedAt": 1776152380034,
"source": "skillhub"
}Classes, Inheritance & Memory
Inheritance Quirks
privatemethods not overridden — new method with same name in childstaticmethods hide, don't override — called based on reference type, not objectsuper()must be first statement in constructor — no logic beforefinalmethods can't be overridden —finalclass can't be extended- Fields don't participate in polymorphism — accessed by reference type
- Constructors not inherited — must define explicitly or get default
@Overrideannotation catches typos — compiler error if not actually overriding
Memory Management
- Leaked listeners/callbacks prevent GC — remove references when done
WeakReferencefor caches — allows GC when memory neededstaticcollections 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-resourcesSoftReferencevsWeakReference— 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 —
permitsclause 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
varfor 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:
permitsin same file — or same package if not explicit non-sealedsubclass 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
Collections & Iteration
Equality Contract
- Override
equals()must also overridehashCode()— HashMap/HashSet break otherwise equals()must be symmetric, transitive, consistent —a.equals(b)impliesb.equals(a)- Use
getClass()check, notinstanceof— 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 arrayList.of(),Set.of()return immutable — throw on modification attemptsHashMapallows null key and values —HashtableandConcurrentHashMapdon't- Sort requires
ComparableorComparator— ClassCastException if missing
Concurrency & Synchronization
Concurrency
volatileensures visibility, not atomicity —count++still needs synchronizationsynchronizedon method locksthis— static synchronized locks class object- Double-checked locking broken without volatile — use holder pattern or enum for singletons
ConcurrentHashMapsafe but not atomic for compound ops — usecomputeIfAbsent()- 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— includesErrorwhich shouldn't be caught finallyalways runs — even on return, but return in finally overrides try's return
Generics & Type Erasure
Generics Traps
- Type erasure: generic type info gone at runtime — can't do
new T()orinstanceof List<String> - Raw types bypass safety —
List listallows any type, loses compile-time checks List<Dog>is not subtype ofList<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, useArrayList<T>instead
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
-Xmxequals-Xmsin 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:dumpand-Xshare:onfor faster startup jcmdfor 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.javaat root — declaresrequires,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 transitiveexposes — readers of your module get the dependency too- Automatic modules for classpath JARs — module name from JAR manifest or filename
--add-opensat runtime — escape hatch when you can't modify module-infoexportscontrols compile-time access —openscontrols runtime reflection- Service provider:
provides X with Y— cleaner than ServiceLoader classpath scanning - Unnamed module for classpath code — can read all named modules
jdepsanalyzes dependencies — use--jdk-internalsto find illegal access
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)orObjects.equals()
Null Handling
- NPE is most common exception — check nulls or use
Optional<T> Optional.get()throws if empty — useorElse(),orElseGet(), orifPresent()- Don't use Optional for fields or parameters — intended for return types
@Nullableand@NonNullannotations help static analysis — not enforced at runtime- Primitive types can't be null — but wrappers (
Integer) can, autoboxing hides this
Autoboxing Dangers
Integer == Integeruses 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)
Streams & CompletableFuture
Streams Advanced
- Streams are single-use — can't reuse after terminal operation, create new stream
findFirst()vsfindAny()— findAny may return different element in parallelflatMap()flattens and maps —Optional.flatMap()unwraps nested Optionalspeek()for debugging only — may not execute with short-circuit ops likefindFirst()toList()(Java 16+) returns unmodifiable —Collectors.toList()is modifiablegroupingBywith downstream —groupingBy(key, counting())for aggregation- Infinite streams need limit —
Stream.iterate()orStream.generate()runs forever reduce()identity must be true identity — wrong value breaks parallel streamscollect()vsreduce()— collect for mutable containers, reduce for immutable- Primitive streams:
IntStream,LongStream— avoid boxing overhead
CompletableFuture Pitfalls
thenApplyvsthenCompose— compose unwraps nested futures, apply doesn't- Exception handling:
exceptionally()recovers,handle()transforms both success/failure join()vsget()— join throws unchecked CompletionException, get throws checked- Default executor is ForkJoinPool.commonPool() — specify custom for blocking I/O
allOf()returnsCompletableFuture<Void>— must extract results manuallyanyOf()returnsCompletableFuture<Object>— loses type safetyorTimeout()andcompleteOnTimeout()— 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
Testing & Serialization
JUnit 5 Traps
@Testhas no expected attribute — useassertThrows(Exception.class, () -> code)@BeforeEachruns per test —@BeforeAllonce per class (must be static)assertEquals(expected, actual)— order matters for failure messagesassertAll()for grouped assertions — reports all failures, not just first@Disabledskips test — not@Ignore(that's JUnit 4)@Nestedfor test organization — inner class inherits@BeforeEach@ParameterizedTestneeds source —@ValueSource,@CsvSource,@MethodSource@TempDirfor temporary files — cleaned up automatically after test
Mockito Pitfalls
when().thenReturn()for stubbing — must call on mock, not real methodverify()checks interaction — call after the action, not before@Mockcreates fake object — all methods return defaults (null, 0, false)@Spywraps real object — real methods called unless stubbed@InjectMocksdoes constructor injection — or setter, or field injectionArgumentCaptorfor complex assertions — capture arguments, verify laterdoReturn().when()for spies — regularwhen()calls real method firstreset()is code smell — usually means test does too much
Serialization Gotchas
serialVersionUIDmust match — mismatch throws InvalidClassExceptiontransientfields 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
Externalizablefor full control — must implementwriteExternal/readExternal