CodeGraph turns a public Java GitHub repository into an explorable code graph. It is built for questions that become awkward in a text search or relational model: what transitively depends on a method, where class-level cycles exist, which classes form the structural core, how one class reaches another, and which methods have no resolved callers.
The analyzer is deliberately described as best-effort static analysis. It parses Java syntax; it does not compile the project or claim to know runtime behaviour.
The fetcher, parser, and loader are separate services. In particular,
java_parser.py returns plain dictionaries and has no database dependency, so its graph
model can be tested with a small fixture repository.
flowchart LR
UI[React + Cytoscape] -->|HTTP| API[FastAPI]
API --> Fetch[URL validation + size check + shallow clone]
Fetch --> Parse[javalang parser]
Parse --> Load[Batched UNWIND loader]
Load --> DB[(CognoDB over Bolt)]
API --> Query[Parameterized Cypher queries]
Query --> DB
graph LR
R[Repository] -->|CONTAINS| F[File]
F -->|DEFINES| C[Class]
C -->|DECLARES| M[Method]
M -->|CALLS| M2[Method]
C -->|EXTENDS| C2[Class]
C -->|IMPLEMENTS| C3[Class]
F -->|IMPORTS| F2[File]
C -->|CALLS_CLASS, derived| C4[Class]
Stored class and method identifiers are prefixed with a deterministic repository ID. This
keeps Class.fqn and Method.fqn globally unique even when two repositories contain the
same Java package. display_fqn preserves the ordinary Java name shown in the UI. Files
use a unique repo_id:path key, which is the scoped form of file-path uniqueness.
The core questions are paths, not rows. A blast-radius query follows a reversed CALLS
relationship for a variable number of hops. Cycle detection asks whether a path returns to
its starting class. The path finder asks for a shortest route across an unknown number of
intermediate classes. In SQL, these require recursive CTEs, repeated self-joins, explicit
cycle guards, and often database-specific path bookkeeping. In openCypher they stay close
to the domain language and return the path itself.
The relationship model is also easy to evolve. EXTENDS, IMPLEMENTS, IMPORTS, and
CALLS remain distinct facts without a wide nullable join table. The loader derives a
CALLS_CLASS edge from method calls once per analysis, making class cycles and shortest
paths both clear and inexpensive enough for the free database tier.
All database calls use driver parameters; no Cypher is assembled with string interpolation.
Runnable, annotated versions live in cypher/.
- Blast radius follows incoming
CALLSedges one to five hops and reports the minimum depth of every transitive caller. - Circular dependencies finds paths of two to six
CALLS_CLASSedges that return to their starting class. The bound makes the result useful and protects a small instance. - Structural core counts distinct caller methods per class and returns the top 20.
- Call path uses
shortestPathacross the derived class call graph, bounded to 12 hops. - Orphan methods finds declarations without a resolved incoming call, excluding common
language entry points such as
mainandtoString.
Prerequisites: Python 3.11+, Node.js 20+, Git, and a CognoDB Cloud instance.
- Create an account in CognoDB Cloud and create a c0/free database.
- Wait until the instance is ready, then copy its Bolt TLS URI, username, and password.
- Keep the password in a password manager; managed graph services commonly show it only
at creation time. This application expects a
bolt+s://...URI. - If the console exposes an IP allowlist, permit the machine running the backend (and the Render service after deployment).
No database credential belongs in source control or in a VITE_ variable: Vite variables
are shipped to the browser.
cd backend
python -m venv .venv
# Windows: .venv\Scripts\activate# macOS/Linux: source .venv/bin/activate
pip install -r requirements-dev.txt
copy .env.example .env # use `cp` on macOS/LinuxFill in COGNODB_URI, COGNODB_USER, and COGNODB_PASSWORD, then:
uvicorn app.main:app --reload
pytestThe API is available at http://localhost:8000; interactive documentation is at /docs.
cd frontend
npm install
copy .env.example .env.local # use `cp` on macOS/Linux
npm run devOpen http://localhost:5173. VITE_API_URL should point at the backend.
With the backend environment configured:
cd backend
python scripts/seed.pyThe seed script intentionally calls the same fetch → parse → load pipeline as the API. Edit
EXAMPLE_REPOSITORIES if a seeded repository exceeds the free-tier cap. Seeding is a
one-time deployment task, not part of web-service startup.
| Method | Endpoint | Purpose |
|---|---|---|
GET | /health | API/database readiness for cold-start handling |
POST | /analyze | Validate, clone, parse, and replace a repository graph |
GET | /repositories | Previously analyzed repositories |
GET | /graph/{repo_id} | Bounded visualization graph |
GET | /node/{fqn} | Properties and immediate neighbors |
GET | /blast-radius/{fqn} | Reverse multi-hop method callers |
GET | /circular-deps/{repo_id} | Bounded class call cycles |
GET | /central-classes/{repo_id} | Most depended-upon classes |
GET | /call-path/{repo_id}?from=...&to=... | Shortest class call path |
GET | /orphan-methods/{repo_id} | Likely uncalled methods |
Expected repository failures have distinct statuses and messages: invalid URL/clone failure
(400), missing or private (404), size/file-count cap (413), clone/parse timeout (408),
and no parseable Java (422). CognoDB connection failures consistently return 503.
javalangis a syntactic parser, not a Java compiler. Overloaded and inherited targets are matched by method name and arity and may be approximate.- Variable types and dynamic dispatch are not resolved. Calls qualified by an instance name often remain unresolved unless there is one unambiguous repository-wide target.
- Calls into external libraries are dropped because their method nodes do not exist.
- Reflection, dependency injection, annotations, framework lifecycle hooks, generated code, and runtime configuration are invisible. They can make an “orphan” a false positive.
- Modern Java syntax unsupported by
javalangis skipped per file and reported in the analysis response. - Parsing is bounded and currently completes before loading begins. A timeout returns a clear error and stores no partial graph, which favors idempotent results over partial data.
- The risk badge is a navigation heuristic (
incoming class calls × transitive caller depth), not an empirical reliability or production-risk measurement.