oracle/graal · error · ElementException

Input field must not be final

Error message

Input field must not be final

What it means

A single-valued input field (annotated @Input or @OptionalInput, type assignable to Node or an interface, not a NodeInputList) must not be final. The compiler runtime replaces input references in-place when edges are rewired (e.g. during canonicalization or when a node is replaced), so the field must stay assignable. The NodeInfo annotation processor raises this at compile time.

Source

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

                if (isNonOptionalInput || isOptionalInput) {
                    if (findAnnotationMirror(annotations, Successor) != null) {
                        throw new ElementException(field, "Field cannot be both input and successor");
                    } else if (isNonOptionalInput && isOptionalInput) {
                        throw new ElementException(field, "Inputs must be either optional or non-optional");
                    } else if (isAssignableWithErasure(field, NodeInputList)) {
                        if (modifiers.contains(FINAL)) {
                            throw new ElementException(field, "Input list field must not be final");
                        }
                        if (modifiers.contains(PUBLIC)) {
                            throw new ElementException(field, "Input list field must not be public");
                        }
                    } else {
                        if (!isAssignableWithErasure(field, Node) && field.getKind() == ElementKind.INTERFACE) {
                            throw new ElementException(field, "Input field type must be an interface or assignable to Node");
                        }
                        if (modifiers.contains(FINAL)) {
                            throw new ElementException(field, "Input field must not be final");
                        }
                        if (modifiers.contains(PUBLIC)) {
                            throw new ElementException(field, "Input field must not be public");
                        }
                    }
                } else if (isSuccessor) {
                    if (isAssignableWithErasure(field, NodeSuccessorList)) {
                        if (modifiers.contains(FINAL)) {
                            throw new ElementException(field, "Successor list field must not be final");
                        }
                        if (modifiers.contains(PUBLIC)) {
                            throw new ElementException(field, "Successor list field must not be public");
                        }
                    } else {
                        if (!isAssignableWithErasure(field, Node)) {
                            throw new ElementException(field, "Successor field must be a Node type");
                        }
                        if (modifiers.contains(FINAL)) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Drop final from the input field declaration.
  2. Keep the field protected or package-private (public is separately rejected).
  3. Initialize the field in the constructor; rely on updateUsages/replaceAtInput APIs for later rewiring.
  4. Rebuild to confirm the processor error is gone.

Example fix

// before
@Input
protected final ValueNode x;

// after
@Input
protected ValueNode x;
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: scanFields() finds modifiers.contains(FINAL) on an @Input/@OptionalInput field in the non-NodeInputList branch (GraphNodeVerifier.java:137-139). Example: '@Input protected final ValueNode operand;'.

Common situations: Writing a new node with final 'value' fields out of habit from value-object design; IDE 'final where possible' refactorings; converting a DTO into a node; copying a constructor-initialized immutable field pattern.

Related errors


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