Languages / Java

Java static analysis: dead code, call graphs and complexity

To find dead code in Java, IntelliJ's unused declaration inspection and PMD work inside one project. repowise builds a call graph across the whole repository and knows how Spring, Jakarta EE, Quarkus and Micronaut reach code by annotation, so @Service beans, @RestController handlers and JPA entities are roots, not false positives. Findings carry confidence and an owner.

Last reviewed: November 2026

Full tier

What repowise detects in Java

Checked against the engine source. Every row runs locally with zero LLM calls.

repowise capabilities for Java
Files parsed.java
Import resolutionFull. Package-to-file index, wildcard and static imports, Gradle settings subprojects and source sets, shared JVM index with Kotlin and Scala
Call graphResolved calls, inheritance and interface implementation
Dead codeUnreachable files, unused exports, zombie packages; reachability from main, stereotype annotations, META-INF/services and JPMS
Health markersComplexity, class metrics, Extract Method plans
Performance markersSpring Data findBy and JDBC calls in loops, string concatenation and Pattern.compile in loops, blocking I/O under a lock
Coverage reportsJaCoCo XML, Cobertura
Frameworks recognisedSpring (stereotypes, @Bean, constructor and field injection, Lombok constructors, request mappings, auto-configuration), Jakarta EE and javax (JAX-RS, CDI, Servlet, EJB, JPA), Quarkus, Micronaut, Camel, Android manifest

Specific to this language

How Java code is read

Spring wiring is in the graph

A @Service that nothing calls directly is still used when Spring injects it. repowise records stereotype annotations, @Autowired, @Inject and @Resource fields, constructor injection including Lombok @RequiredArgsConstructor, @Bean factory methods, Spring Data repositories and @GetMapping or @RequestMapping handlers. AutoConfiguration.imports and spring.factories entries are roots.

Java and Kotlin share one index

Java and Kotlin (and Scala) files resolve through one JVM package index, so a Java class used only from Kotlin is reached, and the reverse. Gradle subprojects from settings.gradle and non-main source sets such as integrationTest are read.

Measured performance risk

The Java dialect flags Spring Data derived queries (findByX, countByX) on a repository and JDBC executeQuery or prepareStatement inside loops, regex compilation in loops and blocking I/O under a lock. Hibernate lazy-load N+1 is a stated ceiling: it is not detected.

Honest comparison

Native Java tools, and when they are enough

Native Java analysis tools
ToolWhat it covers
IntelliJ IDEA inspectionsUnused declarations across a project, with Spring support in Ultimate. Runs in the IDE.
PMDUnusedPrivateMethod, UnusedPrivateField and similar rules, plus CyclomaticComplexity. Private and local scope only for unused code.
SpotBugsBug patterns in bytecode, including some unused fields and dead stores.
JaCoCoLine and branch coverage. Its XML report is read by repowise.
jdepsPackage and module dependencies from compiled classes.

Where repowise adds value

  • Public and file-level dead code across modules, not only private members.
  • Framework roots for Spring, Jakarta EE, Quarkus and Micronaut, so beans and endpoints are not reported.
  • Git history: hotspots, ownership and co-change on the same classes, and confidence that starts from file age.
  • Runs headless with no build: no compile step, no JDK on the analysis machine.

How to run it

Analyse a Java repository

From the repository root. Agents get the same results through the get_dead_code, get_health and get_context MCP tools.

uv tool install repowise        # or: pipx install repowise
repowise init --yes --no-prose     # index the repo, no API key
repowise dead-code --min-confidence 0.7 --safe-only
repowise health --refactoring-targets
./gradlew test jacocoTestReport
repowise coverage add build/reports/jacoco/test/jacocoTestReport.xml
repowise serve                     # graph, health and dead code in a local dashboard

Questions

What people ask before they try it.

How do I generate a call graph for a Java project?

Run repowise init in the repository root. It parses every .java file without compiling, resolves imports through a package index, and records call, inheritance and implementation edges with a confidence each. Explore it with repowise serve or query it from an agent over MCP.

Will it report Spring beans as unused?

No. Classes with Spring stereotypes, injected fields and constructors, @Bean methods, Spring Data repositories and request-mapping handlers are reached through the framework, and @SpringBootApplication and main methods are roots.

Does it read JaCoCo coverage?

Yes. repowise coverage add takes a JaCoCo XML report, detected from its content. Coverage then feeds the health score, test reach on change risk and the coverage gates.

Do I need to build the project first?

No. repowise parses source with tree-sitter and needs no JDK, Maven or Gradle run. Coverage is the only input that comes from a build.

Does repowise send my Java code to an LLM?

No. Parsing, the call graph, dead code, health scores, performance markers and git history all run locally with zero LLM calls. repowise init --yes --no-prose needs no API key, and the analysis itself makes no network calls. Written documentation prose is the one opt-in step that uses a model, and it can run on a local Ollama model.

Index a Java repository.

Public repositories are free on repowise.dev. The CLI is open source and runs on your machine with no API key.