I analyzed 31,670 functions in checkstyle at commit 8dd9b54. Of those, 1,309 fall in the ‘fire’ quadrant, meaning they are structurally complex and were changed recently, and 157 are in the critical band. The five I would look at first are all fire functions, each touched once in the last year and last modified 13 days ago. The top score is an activity-weighted risk of 17.56 for foo11 in a final-local-variable test input. If you are merging into this repository this week, these are the spots where a complex function is also a moving target.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
foo11 | src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/finallocalvariable/InputFinalLocalVariableAssignedMultipleTimes.java | 17.6 | 32 | 6 | 26 |
t | src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/grammar/java14/InputJava14InstanceofWithPatternMatching.java | 16.7 | 35 | 5 | 20 |
search | src/site/resources/js/search.js | 16.6 | 22 | 6 | 15 |
getDocumentedProperties | src/test/java/com/puppycrawl/tools/checkstyle/internal/utils/XdocUtil.java | 14.5 | 10 | 5 | 26 |
foo | src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/equalsavoidnull/InputEqualsAvoidNullNested.java | 13.5 | 8 | 8 | 8 |
31,670 functions analyzed
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Complex Branching×4Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Exit Heavy×4Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×2God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×2Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
One caveat before the function sections: nothing in the top five is a core check implementation. Three are test resource files that exist to be deliberately awkward input for checkstyle’s own checks. The other two are a site search script and a test-side xdoc utility. I am not reading these scores as a judgment on the maintainers, since deliberately convoluted fixtures should score high on structure. What the data does tell me is that supporting code dominates the review priority list, and that the ‘fire’ label is shared by a very large number of functions (1,309 of 31,670), so the top five is a short list drawn from a long one.
I also want to be plain about history: every one of the five has a single commit in its file history, zero bug-linked commits, a bug-fix rate of 0%, no reverts, and one distinct author in the last 90 days. There is no historical defect signal here. I would treat these as maintainability and review-surface findings, not as evidence of problems.
foo11 — InputFinalLocalVariableAssignedMultipleTimes.java
I would start with foo11: it has the highest activity-weighted risk score in the repository (17.56), with a cyclomatic complexity of 32, nesting depth of 6, and fan-out of 26. It was last changed 13 days ago, and it is flagged as both a god function and a long function.
The excerpt shows a test input for the final-local-variable check. It declares local variables that are assigned across many branches (the inline comments mark expected violations for variables like ‘kind’ and ‘m’), with try blocks, instanceof checks, loops, and several early returns. The complexity is the point: a fixture like this has to exercise many assignment paths so the check under test can be verified against them.
The practical risk differs from production code. Because this file is expected-violation input, any edit that shifts a line or branch can change what the test asserts. With 26 distinct calls and several exit paths, it is hard to tell by eye what a small edit does to the expected output. If this fixture needs to grow again, I would split it into smaller input files, one per assignment scenario, rather than extend a 32-path method. That keeps each test’s expected violations readable.
t — InputJava14InstanceofWithPatternMatching.java
t scores an activity-weighted risk of 16.66, with a cyclomatic complexity of 35, nesting depth of 5, and fan-out of 20. It sits under a ‘noncompilable’ resources directory in a java14 grammar folder, and the excerpt shows why: a long run of pattern-matching instanceof expressions combined with negation, ternaries, labeled blocks, loops, and an anonymous class. This is a grammar test, so its job is to cover syntactic combinations, not to be readable.
The flagged patterns (complex branching, deeply nested, exit heavy, god function, long function) mostly reflect that density. The exit-heavy flag carries real meaning here, because the early returns after negated instanceof checks are exactly what the scoping rules under test depend on. My recommendation is modest: group the cases by scoping rule (negation, ternary, loop, labeled block) into separate methods, so a failing grammar test points at one rule rather than at a 35-path method. Since the file is not compiled normally, I would also confirm that any edit is validated by the grammar test that consumes it.
search — search.js
search in the site’s search script has an activity-weighted risk of 16.58, a cyclomatic complexity of 22, nesting depth of 6, and fan-out of 15.
The excerpt shows a scoring function. It normalizes the query, loops over index entries, and assigns a tiered score through an if/else chain (exact title, title prefix, title substring, description, keywords). It then falls back to multi-token matching and a fuzzy subsequence match, and finally sorts and truncates results. Ranking rules, token handling, and fuzzy fallback all sit in one loop body, which is where the nesting depth of 6 comes from.
It is exit-heavy and deeply nested, so each added ranking rule multiplies the cases a test needs. I would extract the scoring ladder into a separate ‘scoreEntry’ function that takes the query, tokens, and entry and returns a number. That leaves search as filter, sort, and slice, and makes the ranking tiers testable without building an index. I did not see a test for this script in the data, so I would confirm one exists before changing the ranking order.
getDocumentedProperties — XdocUtil.java
This one has a different shape. A cyclomatic complexity of 10 is moderate, but fan-out of 26 and nesting depth of 5 push the activity-weighted risk score to 14.47. The function reads a generated xdoc file for a module, finds the ‘Properties’ subsection, and collects the first column of each table row into a set. It throws an IllegalStateException if the file is missing or cannot be parsed.
In a Java codebase, a fan-out of 26 here likely understates the real coupling, because the function leans on file I/O, XML parsing, and the repository’s own helper methods for locating xdoc paths. A change in how xdocs are generated or named would surface here first. It also has multiple exit paths: the not-found throw, the I/O and parser exception translation, and the normal return.
I would separate ‘find the xdoc path for this module’ from ‘parse the properties table’. The path lookup is a loop with a break and a not-found exception, while the parse is DOM traversal. Two small methods would drop the nesting depth and let each be tested with a tiny XML string, without needing generated xdoc files on disk.
foo — InputEqualsAvoidNullNested.java
foo is the cleanest illustration of nesting alone driving a score. Its cyclomatic complexity and fan-out are both only 8, but its nesting depth is 8, the threshold I treat as a strong refactoring signal, and the activity-weighted risk comes out at 13.53. It is the only top-five function whose sole flagged pattern is deep nesting.
The excerpt shows a while loop reading characters from a Reader, with nested conditionals that compare a string against several literals using equalsIgnoreCase, each annotated with an expected violation comment. This is test input for the equals-avoid-null check, so the nesting is purposeful: the check has to find violations inside nested branches.
Since the path count is low, I would not restructure it. Line-sensitive violation comments make any reformatting risky, so I would leave the file alone. If it must change, keep the nesting and add a neighboring fixture rather than editing this one.
What I would do with this list
The lower-ranked functions in the data are in the ‘watch’ quadrant: active, but with a cyclomatic complexity of 8 to 5 and activity-weighted risk scores between 6.74 and 6.90. They are worth watching, not refactoring. I would put the effort where the structure is heavy and the file is moving: search and getDocumentedProperties are the two I would refactor, since they are real code with extractable pieces. The three test fixtures I would leave alone unless they need to grow, and then I would split rather than extend.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
deeply_nested | 5 |
complex_branching | 4 |
exit_heavy | 4 |
god_function | 2 |
long_function | 2 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/checkstyle/checkstyle
cd checkstyle
git checkout 8dd9b5456ff52dc31ebe4a66bf6781b6610ecbe3
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →