# How Java FQN Resolution Handles Package and Class Matching in code-review-graph

> Learn how Java FQN resolution in code-review-graph matches package and class names using a two-step validation process against graph nodes, parent relationships, file stems, and qualified names.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: internals
- Published: 2026-08-16

---

**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`](https://github.com/tirth8205/code-review-graph/blob/main/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.

```python

# 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.

```python

# 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:

1. **Exact match** — The candidate's `qualified_name` exactly matches the target string
2. **Name-only match** — The class and method names match, ignoring package prefix differences
3. **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.

```python

# 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`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/tools/query.py) | Core implementation: `_looks_like_java_method_fqn`, `_java_fqn_candidates`, and disambiguation logic |
| [`tests/test_multilang.py`](https://github.com/tirth8205/code-review-graph/blob/main/tests/test_multilang.py) | Test suite verifying import resolution and package/class matching behavior |
| [`tests/fixtures/SampleJava.java`](https://github.com/tirth8205/code-review-graph/blob/main/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.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/tools/query.py) with comprehensive test coverage in [`tests/test_multilang.py`](https://github.com/tirth8205/code-review-graph/blob/main/tests/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.