JASTG – Java AST Structural Graph
Static structural dependency analysis for Java codebases.
JASTG extracts class-level dependency graphs and object-oriented metrics from Java source code using AST parsing only — no JVM, no classpath, no compilation required. It is designed for reproducible software-engineering research and integrates directly with graph analysis tools such as NetworkX, Gephi, and community-detection algorithms.
Table of Contents
- What JASTG captures
- What JASTG does NOT capture (limitations)
- Installation
- Quick start
- CLI reference
- Python API
- Output formats
- Determinism and reproducibility
- Running tests
- How to cite
- License
What JASTG captures
JASTG extracts structural dependencies between classes based on typed
syntactic signals in the source code. A dependency A → B with weight w
means that class A references class B in w distinct typed positions.
JASTG inspects the following six categories of typed syntactic sites (matching the enumeration in Section 3.1 of the accompanying paper):
(i) extends and implements clauses
class A extends B // A → B
class A implements I // A → I
(ii) Field types
private B field; // A → B
(iii) Method and constructor signatures (return and parameter types)
public B getB() { … } // return type → B
void f(B param) // parameter type → B
A(B param) // constructor parameter type → B
(iv) Object instantiations (ClassCreator)
new B(…) // A → B
(v) Local variable declarations and cast expressions
B local = …; // declared type → B
(B) value // cast target → B
(vi) MethodInvocation qualifier path
B.staticCall() // A → B (resolved under the upper heuristic)
The qualifier path is resolved under a configurable heuristic
(qualifier_heuristic, default upper); see the Limitations section
below for details and modes.
Nested classes
Inner classes are registered as independent nodes with the $ notation
used by the Java Virtual Machine for inner-class binary names:
com.example.Outer$Inner, com.example.Outer$Inner$Deep.
Object-oriented metrics
JASTG computes the following metrics per class. Note that some implementations differ from the canonical definitions in ways documented in the Limitations section and in Section 3.2 of the paper:
| Metric | Definition |
|---|---|
| LCOM4 | Lack of Cohesion of Methods (v4): weakly connected components in the method–attribute graph |
| CBO | Coupling Between Objects: number of distinct internal classes depended on (lower bound — see Limitations) |
| RFC | Response For a Class: NOM + distinct invoked method names (lower bound — see Limitations) |
| NOM | Number of Methods declared in the class (constructors excluded) |
| NOA | Number of Attributes (field declarators) |
| WMC | Weighted Methods per Class: sum of McCabe cyclomatic complexity over the methods counted by NOM (since 1.1.0) |
Limitations
JASTG performs syntactic analysis only — no type solving, no JVM, no classpath. The following are known limitations:
- Type inference (
var, generics inference, lambda return types) is not resolved. - Inner class multilevel dot-notation:
pkg.Outer.Inner.Deepis not resolved. Only the two last parts are converted (Outer.Inner→Outer$Inner;pkg.Outer.Inner→pkg.Outer$Inner). Use$notation in source code if you need these resolved. - Static imports are ignored (they refer to members, not classes).
- Chained method calls (
a.b().c()) are not type-traced. - RFC does not distinguish the class target of each method invocation (inherent limitation without type solving).
- CBO counts only references to classes present in the analysed source tree (external library classes are not nodes).
- Qualifier heuristic (default
--qualifier-heuristic=upper): onlyMethodInvocationqualifiers starting with an uppercase letter are resolved as class references. Classes named with a lowercase first letter would be missed; use--qualifier-heuristic=offto disable.
Installation
From PyPI (once published):
pip install jastg
From source (editable install for development):
git clone https://github.com/MarcosCordeiro/jastg.git
cd jastg
pip install -e ".[dev]"
Requirements: Python ≥ 3.10, javalang ≥ 0.13.0, networkx ≥ 2.6.
Quick start
Single domain
jastg analyze --domain myapp --path /path/to/src
Multiple domains
jastg analyze \
--domain backend --path /path/to/backend/src \
--domain frontend --path /path/to/frontend/src
Undirected, unweighted (for Louvain community detection)
jastg analyze --domain myapp --path /src --undirected --unweighted
From YAML config
jastg analyze --config analysis.yaml
analysis.yaml example:
domains:
- name: backend
path: /path/to/backend
- name: frontend
path: /path/to/frontend
weighted: true
directed: true
qualifier_heuristic: upper
output_dir: output
Check installation
jastg doctor
jastg --version
Try the bundled example
jastg analyze --domain example --path examples/mini_project
CLI reference
jastg analyze [OPTIONS]
Options:
--domain NAME Domain label (repeat for multiple domains)
--path PATH Root path to scan for .java files (paired with --domain)
--config FILE YAML configuration file (alternative to --domain/--path)
--weighted Export edge weights – third column (default: on)
--unweighted Omit edge weights – two-column output
--directed Directed graph (default: on)
--undirected Symmetrize edges (sum reciprocal weights)
--out DIR Output directory (default: output)
--qualifier-heuristic 'upper' (default) or 'off'
--fail-fast Abort on first parse error
-v, --verbose DEBUG-level logging
Python API
from jastg.pipeline import run
metadata = run(
dominios=["myapp"],
caminhos=["/path/to/src"],
ponderado=True, # write edge weights
direcionado=True, # directed graph
output_dir="output",
qualifier_heuristic="upper",
fail_fast=False,
)
print(metadata["numero_classes"]) # number of classes analysed
print(metadata["numero_arestas"]) # number of edges exported
Lower-level API:
import javalang
from jastg.ast.collect import coletar_classes_internas
from jastg.extract import extrair_dependencias_e_metricas
classes, index, domains, n_files = coletar_classes_internas(
["myapp"], ["/path/to/src"]
)
source = open("MyClass.java").read()
tree = javalang.parse.parse(source)
results = extrair_dependencias_e_metricas(tree, "MyClass.java", classes, index, "myapp")
Output formats
All files are written to --out/<domain>/ (default output/<domain>/).
metadata_{domain}.json
Run provenance for reproducibility (e.g. metadata_myapp.json):
{
"project_url": "https://github.com/owner/repo",
"jastg_version": "1.0.0",
"python_version": "3.12.13",
"platform": "Linux-7.0.5-arch1-1-x86_64-...",
"javalang_version": "0.13.0",
"networkx_version": "3.6.1",
"config_hash": "sha256hex...",
"run_date": "2026-02-22T12:00:00+00:00",
"commit_hash": "abc123...",
"num_classes": 9,
"num_edges": 12,
"total_java_files": 7,
"parse_errors": 0,
"directed": true,
"weighted": true
}
graph_{domain}.graphml
GraphML file ready for import into Gephi or any GraphML-compatible tool
(e.g. graph_myapp.graphml).
- Nodes – one per class, with attributes:
label:domain/package.ClassstringLCOM4,CBO,RFC,NOM,NOA,WMC: OO metrics
- Edges – one per dependency pair, with optional
weightattribute (present when--weighted, absent when--unweighted). Undirected mode (--undirected) symmetrizes pairs as(min_id, max_id)and sums reciprocal weights. - Graph-level metadata – all fields from
metadata_{domain}.jsonare embedded directly in the GraphML<graph>element.
Determinism and reproducibility
-
IDs are assigned by alphabetical sort of
domain/classkeys, so they are identical across runs given the same input. -
File traversal uses sorted order to eliminate OS-level non-determinism.
-
Reproducibility fingerprint. The
metadata_{domain}.jsonfile records the full fingerprint required to reproduce a run, composed of:config_hash— SHA-256 digest of the analysis parameters (domains, paths, graph mode, qualifier heuristic)jastg_version,javalang_version,networkx_version,python_version— tool and dependency versions that materially affect parsing and graph constructioncommit_hash— exact source version analysed (when run inside a git repository)
No individual field is sufficient in isolation: the
config_hashalone does not capture dependency versions, and thecommit_hashalone does not capture the analysis configuration. The combination of all fields is what guarantees exact reproduction of a prior run.
Running tests
# All tests (verbose)
pytest -v
# With coverage
pytest --cov=jastg --cov-report=term-missing
# Quick smoke test
pytest -q
The test suite requires no external Java installation. All Java source files are created as strings in memory during the test session.
Benchmark Dataset
The official structural graph benchmark generated using JASTG is available at: https://doi.org/10.5281/zenodo.18744313
How to cite
If you use JASTG in your research, please cite:
@software{jastg2026,
author = {Brito Jr, Marcos Cordeiro de},
title = {{JASTG}: {Java AST Structural Graph}},
year = {2026},
version = {1.0.0},
url = {https://github.com/MarcosCordeiro/jastg},
license = {MIT}
}
See also CITATION.cff in this repository.
License
MIT — see LICENSE.txt.
Metadata
Release files for jastg 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jastg-1.1.0.tar.gz | 30.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jastg-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.2 kB
Release files / jastg-1.1.0.tar.gz
| Download URL | jastg-1.1.0.tar.gz |
|---|---|
| Size | 30.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f263d7933ffb5db01c635aca68cd633f39fe086ab8df89a1029ec7933295761
|
|
BLAKE2b-256 checksum How to use checksums |
792e367065867d1d29b8fd1547f20058b8f6c68dca72c511fb2f12d81ab8b282
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / jastg-1.1.0-py3-none-any.whl
| Download URL | jastg-1.1.0-py3-none-any.whl |
|---|---|
| Size | 28.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
67304f2825c45ad0ec875627285447a4a747420c38c218741e661b30f9a95b3e
|
|
BLAKE2b-256 checksum How to use checksums |
caf4fb3462843ae43635c3bb41da8affb5e828ee435f53b3530d91d6a8179fa5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|