oracle/graal · error · IOException

Unknown field

Error message

Unknown field 

What it means

Thrown when decoding a FIELD_TAG: the codec reflectively resolves a declared field by holder class and name (with special-casing for String.coder/value), and getDeclaredField throws NoSuchFieldException. The IOException 'Unknown field <holder>.<name>' means the field existed when the replay was recorded but not in the replaying JVM's classes.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/BinaryReplayCodec.java:905

            case STACK_TRACE_ELEMENT_TAG ->
                new StackTraceElement(readStringReference(in, state), readStringReference(in, state),
                                readStringReference(in, state), requireNonNullString(readStringReference(in, state), "stack trace holder"),
                                requireNonNullString(readStringReference(in, state), "stack trace method"), readStringReference(in, state), in.readPackedSignedInt());
            case REGISTER_TAG -> findRegister(state.registerArchitecture(), in.readPackedUnsignedInt());
            case FIELD_TAG -> {
                Class<?> holder = ReplayTypeSupport.classCast(ReplayTypeSupport.decodeClass(requireNonNullString(readStringReference(in, state), "field holder")));
                String name = requireNonNullString(readStringReference(in, state), "field name");
                try {
                    if (holder == String.class) {
                        if ("coder".equals(name)) {
                            yield String.class.getDeclaredField("coder");
                        } else if ("value".equals(name)) {
                            yield String.class.getDeclaredField("value");
                        }
                    }
                    yield holder.getDeclaredField(name);
                } catch (NoSuchFieldException e) {
                    throw new IOException("Unknown field " + holder.getName() + "." + name, e);
                }
            }
            case ASSUMPTION_RESULT_TAG -> {
                Object result = readValue(in, state);
                int length = in.readPackedUnsignedInt();
                Assumptions.Assumption[] assumptions = new Assumptions.Assumption[length];
                for (int i = 0; i < length; i++) {
                    assumptions[i] = (Assumptions.Assumption) readValue(in, state);
                }
                yield new Assumptions.AssumptionResult<>(result, assumptions);
            }
            case ASSUMPTION_TAG -> switch (in.readPackedUnsignedInt()) {
                case ASSUMPTION_NO_FINALIZABLE_SUBCLASS ->
                    new Assumptions.NoFinalizableSubclass((ResolvedJavaType) readValue(in, state));
                case ASSUMPTION_CONCRETE_SUBTYPE -> {
                    ResolvedJavaType subtype = (ResolvedJavaType) readValue(in, state);
                    ResolvedJavaType context = (ResolvedJavaType) readValue(in, state);
                    yield new DelayedDeserializationObject.ConcreteSubtypeWithDelayedDeserialization(context, subtype);

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Re-record the replay file on the same JDK + GraalVM build used for replay
  2. Verify the field exists: check holder.getDeclaredField(name) in the replaying environment
  3. If the field genuinely moved, add a resolution case in the FIELD_TAG branch (as done for String 'coder'/'value')

Example fix

// before
yield holder.getDeclaredField(name); // NoSuchFieldException -> IOException: Unknown field

// after
// special-case relocated fields, mirroring the String handling
if (holder == SomeClass.class && "oldName".equals(name)) {
    yield SomeClass.class.getDeclaredField("newName");
}
yield holder.getDeclaredField(name);
Defensive patterns

Strategy: validation

Validate before calling

static boolean fieldResolvable(Class<?> holder, String name) {
    try { holder.getDeclaredField(name); return true; }
    catch (NoSuchFieldException e) { return false; }
}

Try / catch

try {
    result = codec.readReplay(in);
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Unknown field")) {
        // JDK/class version skew: re-record on this JDK
    } else throw e;
}

Prevention

When it happens

Trigger: Replaying against a JDK/compiler build where the field was renamed, removed, or moved to a superclass; the stream is corrupt so the field-name string decodes to garbage.

Common situations: JDK version differences (e.g. String internals changing across JDK releases); replaying files recorded on a GraalVM development build carrying renamed fields; classpath shadowing loading an older class version.

Related errors


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