Java

Java Code Review with LGTM

AI code review for Java: LGTM catches potential NPEs, mutable collection leaks from getters, thread-safety smells, missing equals/hashCode pairs, and Spring Boot anti-patterns.

How LGTM reviews Java PRs

Tree-sitter parses every .java file, extracting class, interface, record, enum, method, and field declarations. Package + import resolution lets the indexer build cross-package symbol graphs.

Java's verbosity means PRs are often larger in line count than equivalent changes in Python or Go. The agents adjust — bigger diffs get longer review windows, more context retrieval, more inline comments.

Java-specific lens: the Bugs agent looks at null safety (Java's old original-sin), the Performance agent at object allocation in hot paths and Stream.collect patterns, the Security agent at SQL building, deserialization, and reflection usage. The Best-Practices agent has light awareness of common framework conventions (Spring, JPA, Jackson).

Common Java bugs LGTM catches

Potential NPEs. Method calls on results of methods that can return null (Map.get, List operations after filter, etc.) without a null check. Flagged with confidence proportional to how 'maybe-null' the source actually is.

Mutable collection leaked from getter. `public List<X> getItems() { return this.items; }` exposes internal state — callers can mutate. The agent recommends Collections.unmodifiableList() or List.copyOf().

Thread-safety smells. Mutable static fields without synchronization, lazy init that's not safely-published, double-checked locking that's missing volatile.

Missing equals/hashCode contract. Class overrides equals but not hashCode (or vice versa). Will break in HashMap/HashSet. Flagged consistently.

@Autowired field injection in Spring. Constructor injection is preferred; field injection prevents testability + introduces subtle init-order bugs. Surfaced as a Best-Practices finding, not a bug — but noted in PRs that introduce new @Autowired fields.

Resource leaks. `FileInputStream fis = new FileInputStream(...);` without try-with-resources. The Bugs agent flags every Closeable instantiation that's not inside try-with-resources.

Tree-sitter coverage for Java

Mature tree-sitter Java grammar. Records, switch expressions, sealed classes (Java 17+), pattern matching (Java 21+) — all parse cleanly.

Build system: Maven (pom.xml) and Gradle (build.gradle / build.gradle.kts) are detected at index time for multi-module project layout. Sub-modules each get their own symbol graph.

Annotations are extracted as part of the symbol metadata. The agents read @Override, @Nullable, @NotNull, @Autowired, @RequestMapping, @Entity to refine their analysis.

Lombok-generated code (getters/setters/builders) is recognised via the @Data, @Builder, @Getter annotations even though the generated methods aren't in source. Our agents don't flag 'missing getter' when @Getter is present.

Setup notes for Java projects

Install the LGTM GitHub App. Index time: 30-120 seconds for typical Spring Boot apps, longer for multi-module Maven/Gradle monorepos.

Generated sources (Lombok output, MapStruct mappers, Protobuf .java) are auto-excluded by common path heuristics (target/generated-sources/, build/generated/).

Test files (Test.java, *Test.java, in src/test/java/) get reviewed with adjusted bar: assertion-style code, looser exception handling, mock-heavy setup are all expected and not flagged as smells.

Multi-module Gradle: each subproject's build.gradle is read; cross-module dependencies resolve via the project graph.

Example bugs LGTM catches

Mutable collection leaked
// ❌ Bug: callers can mutate internal list
public class Cart {
    private final List<Item> items = new ArrayList<>();
    public List<Item> getItems() {
        return items;    // <-- exposed mutable
    }
}

// LGTM finding:
// "Returning internal mutable list breaks encapsulation —
//  callers can mutate without going through Cart's API.
//  Return Collections.unmodifiableList(items) or List.copyOf(items)."
Resource leak without try-with-resources
// ❌ Bug: stream never closed on exception path
public String readFile(Path path) throws IOException {
    FileInputStream fis = new FileInputStream(path.toFile());
    return new String(fis.readAllBytes());   // <-- fis leaked
}

// LGTM finding:
// "FileInputStream is Closeable but not in try-with-resources.
//  Exception during readAllBytes leaks the file handle.
//  Wrap in try (FileInputStream fis = ...) { return ... }."

See LGTM reviewing Java + Spring

Spring-aware · Lombok-aware · multi-module Maven/Gradle support

Go to the product page

Java review FAQs

Spring Boot specifics?

Yes. The agents recognise @RestController, @Service, @Component, @Autowired, @RequestMapping and related Spring patterns. Light-touch awareness — we surface common smells (field injection, controllers doing business logic) but don't enforce arch rules. Use ArchUnit for those.

Lombok support?

Annotation-aware. @Data, @Builder, @Getter, @Setter, @AllArgsConstructor recognised — the agent doesn't false-positive on 'missing equals' if @EqualsAndHashCode is present.

What about Kotlin?

Tree-sitter Kotlin grammar exists but is currently NOT in LGTM's supported language list. Kotlin support is planned. For now, Java repos work fully; Kotlin files inside a Java project get a degraded review (diff-only, no context).

Cost per Java review?

$0.07-$0.18 for a 300-line Java PR on GPT-4o via BYOK. Java's verbose syntax means more tokens per change vs Python/Go. Switch to GPT-4o-mini or Haiku to halve the cost.

Does LGTM run SpotBugs / Checkstyle / PMD?

No. Those are deterministic linters; LGTM is judgment-call AI review. Run them in CI; LGTM catches what they don't articulate — design smells, mutation safety, framework anti-patterns.

Related across LGTM

Other languages