Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage News JEP 457: Streamlining Java Development with the Class-File API

JEP 457: Streamlining Java Development with the Class-File API

JEP 457, Class-File API (Preview), has recently been Integrated for JDK 22. This JEP proposes to provide an API for parsing, generating, and transforming Java class files. This will initially serve as an internal replacement for ASM, the Java bytecode manipulation and analysis framework, in the JDK, with plans to have it opened as a public API. Brian Goetz, the Java language architect at Oracle, characterized ASM as "an old codebase with plenty of legacy baggage" and provided background information on how this draft will evolve and ultimately replace ASM.

At the core of the Class-File API are several key principles. First, it treats class-file entities such as fields, methods, attributes, and bytecode instructions as immutable objects. This immutable representation ensures reliable sharing when a class file undergoes transformations. The API adopts a tree-structured representation to mirror the hierarchical nature of class files, enabling user-driven navigation for efficient parsing. It also emphasizes laziness in parsing, only processing as much of a class file as needed to satisfy the user's requirements.

The Class-File API, housed within the java.lang.classfile package and its subpackages, incorporates three main abstractions: elements, builders, and transforms. Elements are immutable descriptions of class file components. Builders, corresponding to each kind of compound element, facilitate the construction of class files with specific building methods. Transforms represent functions that modify elements during the building process.

The API also introduces new methods for parsing class files using patterns, a departure from the visitor-based approach of ASM. This enables more direct and concise expressions, leveraging Java's pattern-matching capabilities. For instance, developers can iterate through instructions in a CodeModel and match on elements of interest for tasks like dependency graph construction.

Consider the following example:

CodeModel code = ...;
Set<ClassDesc> deps = new HashSet<>();
for (CodeElement e : code) {
    switch (e) {
        case FieldInstruction f  -> deps.add(f.owner());
        case InvokeInstruction i -> deps.add(i.owner());
        // ... and so on for instanceof, cast, etc ...

This snippet demonstrates using pattern matching to parse a Code attribute and collect dependencies for a class dependency graph, iterating through instructions and matching on specific types.

Generating class files with builders is another key feature. The API reverses the traditional idiom of creating a builder with a constructor or factory; instead, clients provide a lambda that accepts a builder. This approach provides more specific and transparent code generation, with the possibility of replaying sequences of operations. It also offers higher-level conveniences for managing block scoping, local-variable index calculation, and label management.

This following code shows how a method can be generated using builders, demonstrating the API's specific and transparent approach to code generation.

ClassBuilder classBuilder = ...;
classBuilder.withMethod("fooBar", MethodTypeDesc.of(CD_void, CD_boolean, CD_int), flags,
    methodBuilder -> methodBuilder.withCode(codeBuilder -> {
        Label label1 = codeBuilder.newLabel();
        Label label2 = codeBuilder.newLabel();
            .invokevirtual(ClassDesc.of("Foo"), "foo", MethodTypeDesc.of(CD_void, CD_int))
            .invokevirtual(ClassDesc.of("Foo"), "bar", MethodTypeDesc.of(CD_void, CD_int))

The transformation capabilities of the Class-File API are noteworthy. Parsing and generation methods align so that transformations occur seamlessly. For example, developers can process a class file to remove specific methods or transform method bodies by applying various transforms.

The following snippet illustrates the API's capabilities for transforming class files, showcasing how class elements can be selectively modified or replaced during the transformation process.

ClassFile cf = ClassFile.of();
ClassModel classModel = cf.parse(bytes);
byte[] newBytes = cf.transform(classModel, (classBuilder, ce) -> {
    if (ce instanceof MethodModel mm) {
        classBuilder.transformMethod(mm, (methodBuilder, me)-> {
            if (me instanceof CodeModel cm) {
                methodBuilder.transformCode(cm, (codeBuilder, e) -> {
                    switch (e) {
                        case InvokeInstruction i
                                when i.owner().asInternalName().equals("Foo") ->
                            codeBuilder.invokeInstruction(i.opcode(), ClassDesc.of("Bar"), 
                                                          i.typeSymbol(), i.isInterface());
                        default -> codeBuilder.with(e);

One of the transformative aspects of JEP 457 is how it addresses the challenges posed by the rapid evolution of the class-file format in the Java ecosystem. By providing a standard API that evolves with the JDK, it ensures that frameworks and tools using this API will automatically support class files from the latest JDK. This ability is crucial for the adoption of new language and VM features that have representations in class files.

In conclusion, JEP 457's Class-File API is a forward-thinking solution that aligns with the modern needs of Java development. Its design principles, abstractions, and transformation capabilities position it as a powerful tool for Java developers, enhancing the efficiency and reliability of class file management in the Java ecosystem​.

About the Author

Rate this Article