java-native-access/jna · error · IllegalArgumentException

OPTIONS must be a public field of type java.util.Map (<cause

Error message

OPTIONS must be a public field of type java.util.Map (<cause>): <mappingClass>

What it means

JNA reads the public static OPTIONS field of a library mapping class and expects it to be a Map<String, Object>. If the field exists but cannot be retrieved as such (ClassCastException on the cast, IllegalAccessException, or other reflection failure other than NoSuchFieldException), JNA throws IllegalArgumentException('OPTIONS must be a public field of type java.util.Map (...)') naming the mapping class.

Source

Thrown at src/com/sun/jna/Native.java:853

        libraryOptions = typeOptions.get(mappingClass);
        if (libraryOptions != null) {
            typeOptions.put(type, libraryOptions);  // cache for next time
            return libraryOptions;
        }

        try {
            Field field = mappingClass.getField("OPTIONS");
            field.setAccessible(true);
            libraryOptions = (Map<String, Object>) field.get(null);
            if (libraryOptions == null) {
                throw new IllegalStateException("Null options field");
            }
        } catch (NoSuchFieldException e) {
            libraryOptions = Collections.<String, Object>emptyMap();
        } catch (Exception e) {
            throw new IllegalArgumentException("OPTIONS must be a public field of type java.util.Map (" + e + "): " + mappingClass);
        }
        // Make a clone of the original options
        libraryOptions = new HashMap<>(libraryOptions);
        if (!libraryOptions.containsKey(Library.OPTION_TYPE_MAPPER)) {
            libraryOptions.put(Library.OPTION_TYPE_MAPPER, lookupField(mappingClass, "TYPE_MAPPER", TypeMapper.class));
        }
        if (!libraryOptions.containsKey(Library.OPTION_STRUCTURE_ALIGNMENT)) {
            libraryOptions.put(Library.OPTION_STRUCTURE_ALIGNMENT, lookupField(mappingClass, "STRUCTURE_ALIGNMENT", Integer.class));
        }
        if (!libraryOptions.containsKey(Library.OPTION_STRING_ENCODING)) {
            libraryOptions.put(Library.OPTION_STRING_ENCODING, lookupField(mappingClass, "STRING_ENCODING", String.class));
        }
        libraryOptions = cacheOptions(mappingClass, libraryOptions, null);
        // Store the original lookup class, if different from the mapping class
        if (type != mappingClass) {
            typeOptions.put(type, libraryOptions);
        }
        return libraryOptions;
    }

View on GitHub (pinned to d036ad9781)

Solutions

  1. Declare OPTIONS exactly as: public static final Map<String, Object> OPTIONS = new HashMap<>();
  2. Make the field public so field.get(null) does not throw IllegalAccessException.
  3. Convert non-Map option holders (Properties, JSON config) into a Map<String, Object> before assignment.
  4. If no options are needed, delete the field — JNA defaults to an empty map on NoSuchFieldException.

Example fix

// before
public interface MyLib extends Library {
    public static final Properties OPTIONS = new Properties(); // not a Map -> IllegalArgumentException
}
// after
public interface MyLib extends Library {
    Map<String, Object> OPTIONS = new HashMap<>();
}
Defensive patterns

Strategy: validation

Validate before calling

Field f = mappingClass.getField("OPTIONS");
if (!Map.class.isAssignableFrom(f.getType())) {
    throw new IllegalArgumentException("OPTIONS must be java.util.Map, found " + f.getType());
}
if (!Modifier.isPublic(f.getModifiers())) {
    throw new IllegalArgumentException("OPTIONS must be public");
}

Type guard

boolean validOptionsField(Class<?> c) {
    try {
        Field f = c.getField("OPTIONS");
        return Map.class.isAssignableFrom(f.getType()) && Modifier.isPublic(f.getModifiers());
    } catch (NoSuchFieldException e) {
        return true; // optional field
    }
}

Try / catch

try {
    lib = Native.load(name, mappingClass, options);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("OPTIONS must be a public field")) {
        throw new IllegalStateException("Fix " + mappingClass.getSimpleName()
            + ".OPTIONS: declare 'public static final Map<String, Object> OPTIONS'", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Declaring 'public static OPTIONS' with a non-Map type (e.g. Properties, HashMap<String,String> assigned where cast fails is fine, but String/int/Map-typed raw mismatch), or a non-public/otherwise inaccessible OPTIONS field that throws IllegalAccessException on get().

Common situations: Typing OPTIONS as something other than java.util.Map; making OPTIONS private/protected and expecting JNA to read it; using a custom Map subtype incompatible with the (Map<String, Object>) cast under strict generics; IDE-generated field with wrong type.

Related errors


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