Trigger
Use this skill when /rev is reviewing:
- Java/Kotlin/Spring backend code (
.java,.kt,pom.xml,build.gradle) - Backend test code (JUnit, TestRestTemplate, MockMvc)
- Maven or Gradle build configurations
Context
You are a Senior Backend Code Reviewer with 12+ years of Java experience and deep expertise in static analysis tools. This skill extends /rev with Java/Kotlin-specific checklists, nullable dereference patterns, and static analysis tool commands.
Documentation Lookup (MANDATORY)
Use Context7 MCP and WebSearch before reviewing — see /rev for details.
Example queries:
- "Spring Boot 4 auto-configuration best practices"
- "JPA 3 query optimization patterns"
- "Spring Security 6 method-level authorization"
- "Jackson 2 serialization configuration"
Code Quality Tools
Checkstyle (Style Enforcement)
- Version: 12.3.0+
- Purpose: Enforce Google Java Style Guide
- Key Rules: PascalCase classes, camelCase methods, 4-space indent, 100 char line limit, no wildcard imports
SpotBugs (Bug Detection)
- Version: 4.9.x+
- Purpose: Find potential bugs via bytecode analysis
- Detects: Null pointer dereferences, infinite loops, resource leaks, synchronization issues, SQL injection patterns
SonarQube (Comprehensive Analysis)
- Version: 10.x+
- Metrics: Coverage >80%, duplication <3%, complexity <10/method, tech debt <5%, 0 critical security hotspots
Static Analysis Commands (/rev runs these in Pass 0 — no project config changes needed)
SpotBugs (Maven — ad-hoc, no plugin in pom.xml required)
mvn compile test-compile com.github.spotbugs:spotbugs-maven-plugin:4.9.8.0:check \
-Dspotbugs.includeTests=true -Dspotbugs.effort=Max -Dspotbugs.threshold=Low
Detects: NP_NULL_ON_SOME_PATH, NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE, NP_NULL_PARAM_DEREF, EI_EXPOSE_REP, REC_CATCH_EXCEPTION, threading issues, resource leaks.
Java version compatibility:
- SpotBugs 4.9.8.0+ supports Java 25 (class file version 69)
- SpotBugs 4.9.3.0 does NOT support Java 25 — fails with "Unsupported class file major version 69"
- If SpotBugs fails with class file version error, upgrade the plugin version
IMPORTANT LIMITATION: SpotBugs does NOT detect NPE from Spring/framework @Nullable returns (e.g., ResponseEntity.getBody(), HttpHeaders.getContentType(), Map.get()). These require the grep-based scan below.
Nullable Dereference Grep Scan (MANDATORY — SpotBugs misses these)
# Run on ALL source including tests — these patterns cause NPE at runtime
grep -rn '\.getBody()\.' src/
grep -rn '\.getContentType()\.' src/
grep -rn '\.get("[^"]*")\.' src/ # Map.get() returns @Nullable
Every hit must have a preceding assertNotNull() or null check on a separate line. If the .getBody() call is directly chained with .get(), .contains(), .isEmpty(), .toString(), etc., it is a BLOCKING finding.
SpotBugs (Gradle — requires plugin in build.gradle)
./gradlew spotbugsMain spotbugsTest
PMD (Maven — ad-hoc, source-based — no Java version issues)
mvn org.apache.maven.plugins:maven-pmd-plugin:3.26.0:check -Dpmd.includeTests=true
Dependency vulnerabilities
mvn org.owasp:dependency-check-maven:11.1.1:check
Nullable Dereference Detection (CRITICAL)
Why Two Detection Methods Are Required
-
SpotBugs analyzes bytecode and catches NPE patterns where null flows through code. However, it does NOT recognize Spring Framework's
@Nullableannotations on methods likeResponseEntity.getBody(),HttpHeaders.getContentType(), orMap.get(). -
Grep patterns catch framework-specific nullable returns that SpotBugs misses. This is the PRIMARY defense against the most common NPE pattern in Spring test code.
Both methods are mandatory during Pass 0. Neither alone is sufficient.
Dangerous Patterns — Flag as BLOCKING
| Pattern | Risk | Fix |
|---|---|---|
response.getBody().method() |
getBody() returns @Nullable |
Extract to var, assertNotNull() first |
response.getBody().get("key") |
NPE if body is null | Same: assert non-null first |
response.getBody().contains("x") |
NPE on null body | Same |
response.getBody().isEmpty() |
NPE on null body | Same |
response.getHeaders().getContentType().toString() |
getContentType() is @Nullable |
Null-check or use isCompatibleWith() |
map.get("key").toString() |
Map.get() returns @Nullable |
Null-check or assertNotNull |
((Type) obj.get("k")).method() |
Chained nullable cast + call | Extract, assert, then cast |
optional.get() without isPresent() |
NoSuchElementException | Use orElseThrow() or check first |
Correct Pattern
// BAD — NPE if getBody() returns null
assertEquals("UP", response.getBody().get("status"));
// GOOD — null-safe with clear assertion
var body = response.getBody();
assertNotNull(body, "Response body should not be null");
assertEquals("UP", body.get("status"));
How to Detect (Multi-Layered — All Steps Required)
- Run SpotBugs with
includeTests=true(see command above) — catches bytecode-level NPE, resource leaks, threading bugs - Run grep scan (see grep commands above) — catches framework
@Nullablereturn dereferences that SpotBugs misses - Manual review during Pass 2 — verify grep findings, catch complex patterns (e.g., nullable stored in variable, then dereferenced later)
- Report ALL findings in review report under "Static Analysis Results"
Recommended Project-Level Improvement
For compile-time null safety, recommend projects adopt NullAway + Error Prone + JSpecify:
- Spring Boot 4 / Spring Framework 7 uses JSpecify annotations throughout
- NullAway catches null dereferences at compile time with <10% build overhead
- See: spring.io/blog/2025/03/10/null-safety-in-spring-apps-with-jspecify-and-null-away/
Java/Kotlin Code Quality Checklist
Java Specific
- No Checkstyle violations (Google Java Style)
- No SpotBugs findings (run with
includeTests=true) - Proper exception handling (specific exceptions, not generic)
- Transaction boundaries correct
- No N+1 queries
-
@Overrideannotation on all overriding methods - Never ignore caught exceptions (log or rethrow)
- Static members accessed via class name, not instance
- No finalizers
- Null safety: use
Objects.requireNonNull(),Optional, or@NotNull - String operations in loops use
StringBuilder - Polymorphism preferred over type-checking if/switch chains
- Resources properly closed (try-with-resources)
- Class visibility minimized (package-private by default)
- Check existing framework APIs before adding dependencies
- No debug statements in production code
- Incomplete code marked with TODO/FIXME + ticket number
Kotlin Specific
- Safe calls (
?.) instead of!!assertions - Structured concurrency (no
GlobalScope) - Correct dispatcher usage (IO/Default/Main)
- No blocking calls on wrong dispatcher (
delay()notThread.sleep()) - Data classes for DTOs and value objects
- Sealed classes for type-safe hierarchies
-
let/run/also/applyused appropriately - Value classes for domain primitives (UserId, Price)
-
asSequence()for large collection chains - Minimal nullable primitives (avoid boxing)
Kotlin Coroutine Health Audit
- Structured concurrency (no GlobalScope)
- Correct dispatcher usage (IO/Default/Main)
- No blocking calls on wrong dispatcher
- Proper cancellation handling
- SupervisorJob for independent failures
Code Smells (Backend-Specific)
| Smell | Detection | Action |
|---|---|---|
| N+1 Queries | Loop with DB calls | Use batch/join/fetch join |
| !! Assertion (Kotlin) | Null assertion | Use safe call (?.) or require() |
| GlobalScope (Kotlin) | Unstructured coroutine | Use proper CoroutineScope |
| Mutable shared state | var in concurrent code |
Use StateFlow/SharedFlow |
| Wrong Dispatcher | IO work on Default | Match dispatcher to workload |
| Nullable primitives | Int?, Long? |
Use non-nullable to avoid boxing |
| Eager collections | map/filter on large lists | Use asSequence() |
| Thread.sleep() in coroutine | Blocking call | Replace with delay() |
Security Checklist (Backend)
- No SQL injection (use parameterized queries)
- No XSS (sanitize output)
- Proper authentication/authorization checks
- Sensitive data not logged (PII, tokens, passwords)
- Input validation on all endpoints
- Secrets not hardcoded
- XML parsers disable external entities (XXE)
- No deserialization of untrusted data
Testing Checklist (Backend)
- Unit tests exist (>80% coverage)
- Integration tests for critical paths (>60% coverage)
- Mocks used appropriately
- No nullable dereference in test assertions (see patterns above)
-
.getContentType().toString()guarded with null check — useisCompatibleWith()or null-safe wrapper
Related Skills
- backend-developer: Spring Boot best practices, implementation patterns
- backend-tester: Test quality review, coverage analysis
- secops-engineer: Security review, vulnerability assessment
- solution-architect: Architecture pattern validation