oracle/graal · error · ElementException

Data field must be final if public

Error message

Data field must be final if public

What it means

Plain data fields (no @Input/@OptionalInput/@Successor, not Node/NodeInputList/NodeSuccessorList typed) that are public must be final. The node's generated meta-access and substitution logic treats public non-final fields as mutable state outside the graph model, which is unsupported; immutable public constants are fine. This is the classic '@NodeInfo data field' rule checked by GraphNodeVerifier at compile time.

Source

Thrown at compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/nodeinfo/processor/GraphNodeVerifier.java:175

                            throw new ElementException(field, "Successor field must not be final");
                        }
                        if (modifiers.contains(PUBLIC)) {
                            throw new ElementException(field, "Successor field must not be public");
                        }
                    }

                } else {
                    if (isAssignableWithErasure(field, Node) && !field.getSimpleName().contentEquals("Null")) {
                        throw new ElementException(field, "Node field must be annotated with @" + Input.getSimpleName() + ", @" + OptionalInput.getSimpleName() + " or @" + Successor.getSimpleName());
                    }
                    if (isAssignableWithErasure(field, NodeInputList)) {
                        throw new ElementException(field, "NodeInputList field must be annotated with @" + Input.getSimpleName() + " or @" + OptionalInput.getSimpleName());
                    }
                    if (isAssignableWithErasure(field, NodeSuccessorList)) {
                        throw new ElementException(field, "NodeSuccessorList field must be annotated with @" + Successor.getSimpleName());
                    }
                    if (modifiers.contains(PUBLIC) && !modifiers.contains(FINAL)) {
                        throw new ElementException(field, "Data field must be final if public");
                    }
                }
            }
            currentClazz = getSuperType(currentClazz);
        } while (!isObject(getSuperType(currentClazz).asType()));
    }

    private AnnotationMirror findAnnotationMirror(List<? extends AnnotationMirror> mirrors, TypeElement expectedAnnotationType) {
        for (AnnotationMirror mirror : mirrors) {
            if (sameType(mirror.getAnnotationType(), expectedAnnotationType.asType())) {
                return mirror;
            }
        }
        return null;
    }

    private boolean isObject(TypeMirror type) {
        return sameType(object.asType(), type);

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Make the public data field final and initialize it in the constructor.
  2. Or reduce its visibility to protected/package-private if it must be mutable (and manage mutation via node methods).
  3. Or model the value as an @Input/@OptionalInput edge if it is actually a node dependency.
  4. Rebuild to confirm.

Example fix

// before
public int offset;

// after
public final int offset;
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: modifiers.contains(PUBLIC) && !modifiers.contains(FINAL) on an unannotated non-node field (GraphNodeVerifier.java:174-176). Example: 'public int offset;' or 'public Stamp stamp;' in a @NodeInfo class.

Common situations: Adding configuration/flag fields to a node and defaulting to public visibility; records-style classes pasted in; exposing cached values publicly without making them final.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/0c163aef0812d741. Report an issue: GitHub.