java-native-access/jna · error · IllegalArgumentException

Structure Callback field '" + field.getName() + "' must be a

Error message

Structure Callback field '" + field.getName() + "' must be an interface

What it means

A Structure field whose type is a Callback must be declared as an interface, because JNA dynamically implements the native trampoline for the callback via a proxy. A concrete (non-interface) Callback class cannot be proxied, so IllegalArgumentException is thrown.

Source

Thrown at src/com/sun/jna/Structure.java:1373

            StructField structField = new StructField();
            structField.isVolatile = Modifier.isVolatile(modifiers);
            structField.isReadOnly = Modifier.isFinal(modifiers);
            if (structField.isReadOnly) {
                if (!Platform.RO_FIELDS) {
                    throw new IllegalArgumentException("This VM does not support read-only fields (field '"
                                                       + field.getName() + "' within " + getClass() + ")");
                }
                // In J2SE VMs, this allows overriding the value of final
                // fields
                field.setAccessible(true);
            }
            structField.field = field;
            structField.name = field.getName();
            structField.type = type;

            // Check for illegal field types
            if (Callback.class.isAssignableFrom(type) && !type.isInterface()) {
                throw new IllegalArgumentException("Structure Callback field '"
                                                   + field.getName()
                                                   + "' must be an interface");
            }
            if (type.isArray()
                && Structure.class.equals(type.getComponentType())) {
                String msg = "Nested Structure arrays must use a "
                    + "derived Structure type so that the size of "
                    + "the elements can be determined";
                throw new IllegalArgumentException(msg);
            }

            int fieldAlignment = 1;
            if (!Modifier.isPublic(field.getModifiers())) {
                continue;
            }

            Object value = getFieldValue(structField.field);
            if (value == null && type.isArray()) {

View on GitHub (pinned to d036ad9781)

Solutions

  1. Convert the Callback class into an interface extending com.sun.jna.Callback (or a JNA-provided sub-interface like Callback.UncaughtExceptionHandler pattern)
  2. Keep an abstract class only as shared helper code, but type the Structure field with the interface
  3. If using StdCallLibrary.StdCallCallback or similar, still define it as an interface

Example fix

// before
public abstract class MyCb implements Callback { public abstract void invoke(); }
class S extends Structure { public MyCb cb; }
// after
public interface MyCb extends Callback { void invoke(); }
class S extends Structure { public MyCb cb; }
Defensive patterns

Strategy: validation

Validate before calling

for (Field f : S.class.getFields()) {
    if (Callback.class.isAssignableFrom(f.getType()) && !f.getType().isInterface()) {
        throw new IllegalStateException(f + " must be an interface");
    }
}

Type guard

static boolean isValidCallbackField(Class<?> t) {
    return !Callback.class.isAssignableFrom(t) || t.isInterface();
}

Try / catch

try {
    s.size();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("must be an interface")) {
        throw new IllegalStateException("Define callback as interface: " + e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Declaring a Structure field whose type is a Callback subclass (abstract or concrete class implementing Callback) instead of an interface that extends Callback.

Common situations: Refactoring a callback into an abstract base class shared by several callbacks; accidentally using a class that implements Callback as the field type; copying C function-pointer typedefs into a class rather than an interface.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12). Data as JSON: /api/errors/a86050efbd37486a. Report an issue: GitHub.