How Java FQN Resolution Handles Package and Class Matching in code-review-graph
Java fully-qualified name (FQN) resolution in code-review-graph uses a two-step validation process that detects FQN-shaped strings and matches them against graph nodes using parent relationships, file stems, or qualified names.
The code-review-graph library provides sophisticated resolution of Java method calls expressed as fully-qualified names. Understanding how it matches packages and classes is essential for building accurate code review tools and graph-based analysis systems. This article examines the complete resolution pipeline implemented in the project's query tools.
Detecting FQN-Shaped Targets
The first phase identifies whether a target string actually represents a Java FQN. The _looks_like_java_method_fqn function in code_review_graph/tools/query.py applies strict criteria to filter out ordinary module names and non-Java identifiers.
Validation Criteria
The function validates three specific properties:
- Identifier characters only — Each segment must contain only
[A-Za-z_$][A-Za-z0-9_$]* - Dot-separated structure — The string must use
.as the separator - Class name position — The penultimate segment must start with an uppercase letter, matching Java class naming conventions
This ensures only strings resembling package.Class.method (or longer chains like package.subpackage.Class.method) proceed to candidate matching. The uppercase check on the penultimate segment is particularly important—it distinguishes class names from package names, which typically use lowercase.
# Example targets that pass validation
"com.example.utils.StringUtils.capitalize" # ✓ Valid FQN
"org.apache.commons.io.FileUtils.readFileToString" # ✓ Valid FQN
# Example targets that fail validation
"python_module.submodule" # ✗ Lowercase penultimate segment
"just.a.random.path" # ✗ No class-like component
Finding Candidate Nodes
Once a target passes the FQN shape check, _java_fqn_candidates performs the actual graph search. This function splits the target into class_name and method_name components, then queries the graph store for Java nodes matching the method name.
Evidence Sources for Class Matching
For each method name match, the resolver validates one of three evidence sources to confirm the class relationship:
Parent match — The node's parent (typically the enclosing class or interface) ends with the extracted class_name. This leverages the graph's hierarchical structure where classes contain methods.
File-stem match — The source filename without extension equals class_name. This handles common Java conventions where public classes match their containing files.
Qualified-name match — The node's qualified_name attribute ends with class_name.method_name. This provides a direct string match against fully-resolved identifiers.
Any candidate satisfying at least one check is returned as a possible resolution. The design intentionally accepts multiple validation paths to accommodate varying code patterns and graph construction scenarios.
# Example: How candidates are evaluated
target = "com.example.utils.StringUtils.capitalize"
# Split into components
class_name = "StringUtils"
method_name = "capitalize"
# Graph query finds all Java nodes with name="capitalize"
# Then filters by one of the three evidence sources above
candidates = _java_fqn_candidates(store, target)
Handling Ambiguous Matches
When multiple candidates satisfy the validation criteria, the query tool does not arbitrarily select one. Instead, it returns an ambiguous status with all candidates ranked by match quality.
Ranking Priority
The disambiguation hierarchy follows this order:
- Exact match — The candidate's
qualified_nameexactly matches the target string - Name-only match — The class and method names match, ignoring package prefix differences
- Substring match — The target appears as a substring within the candidate's qualified name
This ranking is implemented in the query tools that consume _java_fqn_candidates, including get_impact_radius and other MCP graph-query commands. Callers receive the full candidates list and can resolve ambiguity by supplying the precise qualified_name from the returned options.
# Example 3: Ambiguous resolution requiring caller disambiguation
target = "Helper.process"
candidates = _java_fqn_candidates(store, target)
# Returns multiple nodes:
# - Node 1: qualified_name="org.foo.Helper.process"
# - Node 2: qualified_name="org.bar.Helper.process"
# Caller resolves by using the full qualified name
resolved_target = "org.foo.Helper.process" # Explicit selection
Design Trade-offs
The resolution architecture balances two competing requirements:
Precision — The strict FQN shape detection prevents false positives on dotted paths that aren't Java identifiers. The class naming convention check ([A-Z] start on penultimate segment) is a heuristic that correctly identifies the vast majority of Java code while filtering out module-style names common in other languages.
Safety — Explicit ambiguity reporting prevents silent errors. Rather than guessing which Helper.process implementation the user intended, the system surfaces all possibilities and requires explicit selection. This is critical for code review tools where incorrect method attribution could lead to missed dependencies or false security analysis results.
Key Implementation Files
| File | Purpose |
|---|---|
code_review_graph/tools/query.py |
Core implementation: _looks_like_java_method_fqn, _java_fqn_candidates, and disambiguation logic |
tests/test_multilang.py |
Test suite verifying import resolution and package/class matching behavior |
tests/fixtures/SampleJava.java |
Fixture source for exercising FQN resolution in tests |
Summary
- FQN detection requires dot-separated Java identifiers with an uppercase class name in the penultimate position
- Candidate matching uses three parallel validation strategies: parent relationships, filename stems, and qualified name suffixes
- Ambiguity handling returns ranked candidates rather than guessing, enabling caller-controlled disambiguation
- Implementation resides in
code_review_graph/tools/query.pywith comprehensive test coverage intests/test_multilang.py
Frequently Asked Questions
What qualifies as a valid Java FQN in this system?
A valid Java FQN must contain only Java identifier characters, use dots as separators, and have at least two components with the penultimate component starting with an uppercase letter. This last requirement distinguishes class names from package names, following standard Java naming conventions.
How does the resolver handle inner classes?
Inner classes are supported through the parent match evidence source. Since the graph preserves parent-child relationships, an inner class method like OuterClass.InnerClass.method will match when the node's parent chain ends with the specified class name sequence.
What happens when no candidates match?
When _java_fqn_candidates finds no nodes satisfying any of the three evidence checks, it returns an empty list. The calling query tool typically reports this as an unresolved reference, which may trigger fallback handling or be reported to the user as an unknown method.
Can the resolution work with partial package names?
The current implementation expects complete package paths in the target string. Partial matching (e.g., StringUtils.capitalize without package) is supported only through the ambiguity resolution mechanism—if multiple classes named StringUtils exist, all will be returned as candidates for caller selection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →