Skip to content
Luke Hutchison edited this page Sep 23, 2026 · 7 revisions

The classgraph-viz library renders a scan result as a GraphViz .dot file, which GraphViz can then lay out and turn into an image. Replace X.Y.Z with the latest release number:

<dependency>
    <groupId>io.github.classgraph</groupId>
    <artifactId>classgraph-viz</artifactId>
    <version>X.Y.Z</version>
</dependency>

classgraph-viz depends on classgraph, so this one dependency is all you need -- it brings the scanner with it.

Contents

Generating a graph

import io.github.classgraph.*;
import io.github.classgraph.viz.*;
import java.nio.file.Path;

try (ScanResult scanResult = new ClassGraph().enableNonSystemModules().enableClasspath()
        .acceptPackages("com.xyz")
        .enableClassInfo().enableFieldInfo().enableMethodInfo().enableAnnotationInfo()
        .scan()) {
    GraphVizDotFile.write(scanResult, scanResult.getAllClasses(), Path.of("classgraph.dot"));
}

Then run GraphViz over the file:

dot -Tsvg classgraph.dot > classgraph.svg

Use GraphVizDotFile.generate(...) instead of write(...) if you want the .dot file contents as a String rather than written to a file. The file is written in UTF-8.

See example graph output here

See the graph legend here

The two kinds of graph

generate / write draw the classes themselves: each class is a box listing its fields, methods and annotations, and the boxes are connected to superclasses, superinterfaces, and the types of fields, method parameters and return values. Class boxes are colored by kind: standard classes yellow, interfaces blue, annotations purple.

This graph cannot show a dependency that only exists inside a method body -- a class referenced by a local variable or an intermediate value. For those, use the second kind of graph.

generateFromInterClassDependencies / writeFromInterClassDependencies draw only the class names, connected by "depends upon" edges. This needs ClassGraph#enableInterClassDependencies() before scanning, and covers every kind of reference, including references inside method bodies. There is only one arrow type: it says that one class depends upon another in some way, without saying how. The exact relationship cannot be recovered without parsing the full bytecode of every method, which ClassGraph does not attempt -- these dependencies are found from the types of fields and methods, and from the class names in each class' constant pool.

GraphVizDotFile

Every method is static, and takes the ScanResult the classes came from, and the ClassInfoList to plot (e.g. scanResult.getAllClasses(), or any list you have filtered down yourself).

Method Returns
String generate(ScanResult, ClassInfoList) The .dot file contents, with default options.
String generate(ScanResult, ClassInfoList, GraphVizDotFileOptions) The .dot file contents.
Path write(ScanResult, ClassInfoList, Path) Writes the .dot file in UTF-8, and returns the path. Throws IOException.
Path write(ScanResult, ClassInfoList, Path, GraphVizDotFileOptions) The same, with options.
String generateFromInterClassDependencies(ScanResult, ClassInfoList) The inter-class dependency graph, with default options.
String generateFromInterClassDependencies(ScanResult, ClassInfoList, GraphVizDotFileOptions) The inter-class dependency graph.
Path writeFromInterClassDependencies(ScanResult, ClassInfoList, Path) Writes the inter-class dependency graph. Throws IOException.
Path writeFromInterClassDependencies(ScanResult, ClassInfoList, Path, GraphVizDotFileOptions) The same, with options.

generate and write throw IllegalStateException if ClassGraph#enableClassInfo() was not called before scanning, and the ...FromInterClassDependencies methods throw it if ClassGraph#enableInterClassDependencies() was not called -- in either case there would be nothing to graph.

GraphVizDotFileOptions

A freshly constructed GraphVizDotFileOptions holds the defaults. Each method sets one option and returns this, so options can be chained:

GraphVizDotFile.generate(scanResult, scanResult.getAllClasses(),
        new GraphVizDotFileOptions().setLayoutSize(12, 8).hideFields().hideMethods());

Every option has a method for each of its settings, so an instance you were handed from elsewhere can be set to either value, not only changed away from the default:

Methods Effect Default
setLayoutSize(float sizeX, float sizeY) The image output size in inches, for when GraphViz renders the file. 10.5 by 8 inches
showFields() / hideFields() Show or do not show fields within class boxes. shown
showFieldTypeDependencyEdges() / hideFieldTypeDependencyEdges() Draw or do not draw edges from a class to the types of its fields. drawn
showMethods() / hideMethods() Show or do not show methods within class boxes. shown
showMethodTypeDependencyEdges() / hideMethodTypeDependencyEdges() Draw or do not draw edges from a class to its methods' return and parameter types. drawn
showAnnotations() / hideAnnotations() Show or do not show annotations within class boxes. Covers annotations on the class, and on its fields, its methods and their parameters. shown
showAnnotationDependencyEdges() / hideAnnotationDependencyEdges() Draw or do not draw edges from a class to the annotations on it. drawn
useSimpleNames() / useFullyQualifiedNames() Show class names in field and method type signatures and in annotations with the package name stripped, or fully qualified. package name stripped
includeExternalClasses() / excludeExternalClasses() Show or do not show external (non-accepted) classes in the inter-class dependency graph. includeExternalClasses() only has an effect if ClassGraph#enableExternalClasses() was called before scanning; excludeExternalClasses() hides them even if it was. follows the scan's own setting

The inter-class dependency graph reads only setLayoutSize, includeExternalClasses and excludeExternalClasses -- the options that show or hide the contents of a class box have no effect on it, since it does not draw class contents.

What has to be enabled before scanning

A class box only shows what the scan actually collected, so enable the information you want to see before scanning:

To show Call before scanning
Classes at all (required) enableClassInfo()
Non-public classes ignoreClassVisibility()
Fields enableFieldInfo()
Non-public fields ignoreFieldVisibility()
Methods enableMethodInfo()
Non-public methods ignoreMethodVisibility()
Annotations enableAnnotationInfo()
Non-public annotation classes as nodes of their own ignoreClassVisibility() (an annotation is a class)
The inter-class dependency graph enableInterClassDependencies()
External (non-accepted) classes enableExternalClasses()

No method in the table is called for you by any other method, so call the method for every row you want. The accept and reject methods do not enable class scanning either. Each ignore...Visibility() method needs the enable...Info() method in the row above it: without it, scan() throws IllegalArgumentException.

Method parameter names only appear in the graph if the code being scanned was compiled with javac -parameters. In Eclipse this setting is Project Properties > Java Compiler > Store information about method parameters (usable via reflection).

Clone this wiki locally