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.

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.

Appendix: 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)