codeanalyzer-java
The codeanalyzer-java backend is the JVM-based static analysis backend for Java in the CLDK Python SDK. It uses WALA (T.J. Watson Libraries for Analysis) for semantic analysis and Javaparser for syntactic extraction. It writes one JSON schema, and the Python SDK deserializes that schema into typed models.
Overview
Section titled “Overview”codeanalyzer-java is a standalone JAR tool. It takes a Java project, or a source string, and writes a JSON graph that contains:
- Symbol table: All types (classes, interfaces, enums, records), their fields, methods/constructors, and imports
- Call graph (optional, at analysis level 2): Edges between callers and callees, computed by the interprocedural analysis of WALA
- Type hierarchy: Extends/implements relationships, nested types, parent-child relationships
- Comments and Javadoc: The text and the position of each comment, attached to its entity
- Entrypoints: Main methods and framework-identified entrypoints (REST endpoints, Struts actions)
The Python SDK runs this backend, parses the JSON, and wraps it in JavaAnalysis. You therefore work with an analysis object, not with the backend. The backend is a JAR.
2.0 The optional extra cldk[java] installs the codeanalyzer-java wheel. The wheel carries the JAR and a bundled JVM (jdk4py), and the SDK runs the JAR on that JVM. Analysis level 2 and above also need a JDK 11 or later on JAVA_HOME, because the backend builds the project with Maven or Gradle. There is no separate JAR download, and you do not set analysis_backend_path. In CLDK 1.5.0, the SDK downloads a Temurin JDK on first use instead.
Architecture
Section titled “Architecture”Analysis pipeline
Section titled “Analysis pipeline”The backend has these stages:
graph LR
A["Java source files"] --> B["Javaparser<br/>+ Symbol Solver"]
B --> C["Symbol Table"]
C --> D["Type & Method<br/>Extraction"]
D --> E["JSON Serialization"]
E --> F["analysis.json"]
G["Compiled binaries<br/>jar/ear/war"] --> H["WALA<br/>ClassHierarchy"]
H --> I["Call Graph<br/>Construction"]
I --> J["Call Graph JSON"]
J --> F
Key stages:
- Javaparser + Symbol Solver: Javaparser parses each
.javafile into an AST. The symbol solver resolves the types against the available libraries. - Symbol table extraction: The backend walks the AST and collects the classes, methods, fields, comments, and imports.
- WALA analysis (level 2 and above): WALA builds the class hierarchy and the interprocedural call graph from the compiled classes.
- JSON export: Gson serializes all entities into the canonical shape (symbol table and call graph edges).
Package structure
Section titled “Package structure”com.ibm.cldk├── CodeAnalyzer.java # CLI entry point, orchestrates analysis├── SymbolTable.java # Javaparser-based symbol extraction├── SystemDependencyGraph.java # WALA-based call graph construction├── entities/ # Output data model classes│ ├── JavaCompilationUnit.java # A single .java file: types + imports│ ├── Type.java # Class/interface/enum/record│ ├── Callable.java # Method/constructor│ ├── Field.java # Member field│ ├── Comment.java # Javadoc/inline comment│ ├── Import.java # Import declaration│ ├── CallableVertex.java # Call graph node (method)│ ├── CRUDOperation.java # Database operation marker│ └── ...├── javaee/ # Framework-specific finders│ ├── EntrypointsFinderFactory # Detects main, REST, Struts entry points│ ├── CRUDFinderFactory # JDBC/JPA/Hibernate CRUD detection│ └── spring/, struts/, ... # Framework integrations└── utils/ ├── BuildProject.java # Maven/Gradle build invocation ├── ScopeUtils.java # WALA scope setup └── AnalysisUtils.java # Helpers for type resolutionCore dependencies:
- WALA 1.6.7: Interprocedural call graph, class hierarchy, points-to analysis
- Javaparser: Source code parsing, type resolution
- Picocli: Command-line interface
- Gson: JSON serialization
Output schema
Section titled “Output schema”The backend writes one JSON document. With the default --schema v2, the document is an envelope with schema_version, language, max_level, analyzer, and application. The SDK reads only that shape. Gson writes every key in lower_case_with_underscores, for example file_path and type_declarations, never filePath. The legacy --schema v1 shape is the one below:
{ "version": "2.3.7", "symbol_table": { "/absolute/path/to/File.java": { "filePath": "/absolute/path/to/File.java", "packageName": "com.example", "comments": [ ... ], "imports": [ ... ], "typeDeclarations": { "ClassName": { ... } } } }, "call_graph": [ ... ]}Symbol table structure
Section titled “Symbol table structure”Compilation unit (JavaCompilationUnit)
Section titled “Compilation unit (JavaCompilationUnit)”A single .java source file:
{ filePath: string // Absolute path packageName: string // Package declaration comments: JComment[] // File-level comments imports: JImport[] // All import declarations typeDeclarations: { [typeName: string]: JType // Map of top-level types } isModified?: boolean // Flag for incremental updates}Type (JType)
Section titled “Type (JType)”A class, interface, enum, or record:
{ isClassOrInterfaceDeclaration: boolean isEnumDeclaration: boolean isAnnotationDeclaration: boolean isRecordDeclaration: boolean isInterface: boolean isNestedType: boolean isInnerClass: boolean isLocalClass: boolean
modifiers: string[] // "public", "abstract", etc. annotations: string[] // "@Override", "@Deprecated", etc.
extendsList: string[] // Superclass names (fully qualified) implementsList: string[] // Interface names parentType: string | null // For nested/inner types nestedTypeDeclarations: string[] // Names of inner types
fieldDeclarations: JField[] // All fields callableDeclarations: { [signature: string]: JCallable // Methods + constructors } enumConstants: JEnumConstant[] // For enums recordComponents: JRecordComponent[] // For records initializationBlocks: JInitializationBlock[] comments: JComment[]
isEntrypointClass: boolean // true for main(String[]) classes}Callable (JCallable)
Section titled “Callable (JCallable)”A method or constructor:
{ signature: string // "methodName(Type1, Type2)" declaration: string // Full declaration with modifiers
modifiers: string[] // "public", "static", "final", etc. annotations: string[] returnType: string | null // null for constructors thrownExceptions: string[]
parameters: JCallableParameter[] // Method parameters comments: JComment[]
code: string // Method body source filePath: string startLine: number // Location in source endLine: number codeStartLine: number // Where body starts
callSites: JCallSite[] // Calls made within this method referencedTypes: string[] // Types referenced in body accessedFields: string[] // Fields read/written variableDeclarations: JVariableDeclaration[]
cyclomaticComplexity: number isConstructor: boolean isImplicit: boolean // Generated (e.g., default constructor) isEntrypoint: boolean // main or framework entry point}Call graph edges
Section titled “Call graph edges”At analysis level 2, the output has a call_graph array. Each edge has this shape:
{ source: { // Caller filePath: string typeDeclaration: string signature: string callableDeclaration: string } target: { // Callee filePath: string typeDeclaration: string signature: string callableDeclaration: string } type: string // "CALL" | "INVOKE_SPECIAL", etc. weight: string // Call count (usually "1")}Comment (JComment)
Section titled “Comment (JComment)”{ content: string // Comment text startLine: number endLine: number startColumn: number endColumn: number isJavadoc: boolean // true for /** ... */}Import (JImport)
Section titled “Import (JImport)”{ path: string // Fully qualified name or wildcard isStatic: boolean isWildcard: boolean // true for "import ....*"}CLI interface
Section titled “CLI interface”Run the JAR with java -jar codeanalyzer-*.jar, or with the canjv launcher. pip install codeanalyzer-java installs canjv. The flags are:
Usage: java -jar codeanalyzer.jar [-hvV] [--no-build] [-a=<analysisLevel>] [-b=<build>] [-i=<input>] [-o=<output>] [-s=<sourceAnalysis>]
Convert a Java project into a system dependency graph.
-i, --input=<input> Path to the project root directory. -s, --source-analysis=<sourceAnalysis> Analyze a single string of java source code instead of the project. -o, --output=<output> Destination directory to save the output graphs. By default, the SDG formatted as a JSON will be printed to the console. -b, --build-cmd=<build> Custom build command. Defaults to auto build. --no-build Do not build your application. Use this option if you have already built your application. -a, --analysis-level=<analysisLevel> Level of analysis to perform. Options: 1 (for just symbol table) or 2 (for call graph). Default: 1 -v, --verbose Print logs to console. -t, --target-files=<targetFiles> For each file, perform source analysis on top of existing analysis.json -h, --help Show this help message and exit. -V, --version Print version information and exit.Key parameters
Section titled “Key parameters”| Flag | Purpose | Example |
|---|---|---|
-i, --input | Project root directory | -i /path/to/commons-cli |
-s, --source-analysis | Single Java source string (no project needed) | -s "public class Test {}" |
-o, --output | Output directory for analysis.json | -o ./analysis_output |
-a, --analysis-level | 1 symbol table, 2 adds the call graph, 3 adds CFG, CDG, and DDG per callable, 4 adds the interprocedural SDG | -a 2 |
-b, --build-cmd | Custom build command (Maven/Gradle) | -b "mvn clean install" |
--no-build | Skip the build. Use pre-compiled binaries | --no-build |
-v, --verbose | Print debug logs | -v |
-t, --target-files | Incremental analysis on specific files. Repeat the flag for each file. Needs --schema v1, and level 2 and above fall back to level 1 | --schema v1 -t src/Main.java -t src/Helper.java |
--schema | Output schema: v2 (default, canonical) or v1 (legacy) | --schema v1 |
--emit | Output target: json (default), neo4j (graph.cypher, or a live Bolt push with --neo4j-uri), or schema | --emit neo4j |
--app-name | Application name for the graph :JApplication anchor (default: input directory name) | --app-name payments |
--neo4j-uri | Push the graph to a live Neo4j over Bolt. Also reads NEO4J_URI | --neo4j-uri bolt://localhost:7687 |
-c, --cache-dir | Directory for the incremental analysis cache | -c ./output/cache |
--eager | Ignore the cache and rebuild everything | --eager |
Examples
Section titled “Examples”Analyze a project at symbol-table level (local files only):
java -jar codeanalyzer-2.3.7.jar \ -i /path/to/commons-cli \ -a 1 \ -o ./outputFull analysis with call graph (build is automatic by default):
java -jar codeanalyzer-2.3.7.jar \ -i /path/to/commons-cli \ -a 2 \ -o ./output \ -vAnalyze a single source string (no build):
java -jar codeanalyzer-2.3.7.jar \ -s "public class HelloWorld { public static void main(String[] args) {} }" \ -a 1Incremental analysis on specific files:
java -jar codeanalyzer-2.3.7.jar \ -i /path/to/commons-cli \ -t src/main/java/org/apache/commons/cli/Option.java \ -o ./outputHow the Python SDK uses it
Section titled “How the Python SDK uses it”The Python SDK (JavaAnalysis) runs the backend for you:
- Backend discovery: The SDK imports
codeanalyzer_javafrom thecldk[java]extra and callscommand(), which returns[java, -jar, codeanalyzer.jar]with the bundled JVM. If you did not install the extra, the SDK raises an error that namespip install "cldk[java]". - Run: The SDK runs the backend with
-i <project> -a <level> -o <cache_dir> -c <cache_dir>/cache -v --app-name <project name>. Without a cache directory, the SDK reads the JSON from stdout. - JSON parsing: Deserializes the output into Pydantic models such as
JApplication,JType, andJCallable - Facade: Wraps the models in
JavaAnalysis, which gives you query methods such asget_classes(),get_call_graph(), andget_callers()
from cldk import CLDKfrom cldk.analysis import AnalysisLevel
# Behind the scenes:# 1. CLDK.java(...) selects the codeanalyzer-java backend (default CodeAnalyzerConfig)# 2. Runs the jar on the bundled JVM: java -jar codeanalyzer.jar -i commons-cli -a 2 -o <cache_dir># 3. Parses JSON -> JApplication (symbol_table + call_graph)# 4. Returns JavaAnalysis
analysis = CLDK.java( project_path="commons-cli", analysis_level=AnalysisLevel.call_graph,)The CLDK(language="java").analysis(...) form is deprecated. Use the CLDK.java(...) factory shown above. The from cldk import CLDK import is the same for both forms.
To control where the SDK caches analysis artifacts, pass a backend configuration:
from cldk import CLDKfrom cldk.analysis import AnalysisLevelfrom cldk.analysis.commons.backend_config import CodeAnalyzerConfig
analysis = CLDK.java( project_path="my_project", analysis_level=AnalysisLevel.call_graph, backend=CodeAnalyzerConfig(cache_dir="/tmp/analysis-cache"),)Keyword arguments
Section titled “Keyword arguments”CLDK.java(...) accepts project_path, analysis_level, target_files, and eager. The type of the backend= configuration selects the backend. For Java, omit backend= for the default in-process codeanalyzer, or pass backend=CodeAnalyzerConfig(cache_dir=...) to change where artifacts are cached. Pass backend=Neo4jConnectionConfig(...) for the read-only Neo4j backend (see Analysis at scale). Caching is on by default. The cache root defaults to <project>/.codeanalyzer, and artifacts go under a language-keyed subdirectory (<cache_dir>/java/).
In CLDK 1.5.0, CLDK.java(...) also accepts source_code=... for a single Java source string, and that argument is deprecated. 2.0 CLDK 2.0 removes the argument, and CLDK.java(source_code=...) raises an error. Use project_path. To analyze one source string directly, run the CLI with --schema v1 -s.
Import the configuration objects from:
from cldk.analysis.commons.backend_config import CodeAnalyzerConfigBuilding & testing
Section titled “Building & testing”To build codeanalyzer-java from source:
-
Clone the repository
Terminal window git clone https://github.com/IBM/codeanalyzer-javacd codeanalyzer-java -
Install a JDK 11 or later with SDKMAN
Terminal window sdk install java 17.0.10-sem # Or your preferred JDK -
Build the JAR
build/libs/codeanalyzer-2.3.7.jar ./gradlew fatJar -
Test on a sample project
Terminal window java -jar build/libs/codeanalyzer-2.3.7.jar \-i src/test/resources/test-applications/call-graph-test \-a 2 -v
Schema stability
Section titled “Schema stability”The JSON schema from codeanalyzer-java is versioned. Each schema v2 output carries schema_version, max_level, and analyzer.version. The Pydantic models of the Python SDK (JAnalysis, JApplication, JType, and others) read schema v2 only. If a cached analysis.json has no schema_version, the SDK raises an error and asks you to remove the file or pass eager=True. If the backend did not fully compute the requested level, the SDK logs a warning.
If you integrate codeanalyzer-java directly instead of through the Python SDK, make sure that the consumer can handle the schema for the version in use.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
java: command not found | No JDK on PATH when you run the JAR directly | Install a JDK 11 or later. Add its bin directory to PATH |
Cannot find symbol type X | Missing library dependencies | Use --no-build for pre-compiled code. Make sure that dependencies are on the classpath |
analysis.json is corrupt | Incomplete write or killed process | Make sure that there is disk space. Run the analysis again |
Existing analysis.json uses legacy import schema | A JAR 2.3.7 or later read an analysis.json that an older JAR wrote | Remove the old analysis.json, then run the analysis again |
RTA call graph unavailable | No JDK with javac on JAVA_HOME, so the build failed, or the project has no entry point | Set JAVA_HOME to a JDK 11 or later. Make sure that the project has a main(String[]) method or a framework entry point |
See also
Section titled “See also”- Source repository: codellm-devkit/codeanalyzer-java
- WALA documentation: wala.dev
- Javaparser: javaparser.org
- Related: codeanalyzer-python, codeanalyzer-ts
- How to: Add a language backend
To report a limitation, open an issue with details. See Contributing for how to extend the backend.