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
- Declare OPTIONS exactly as: public static final Map<String, Object> OPTIONS = new HashMap<>();
- Make the field public so field.get(null) does not throw IllegalAccessException.
- Convert non-Map option holders (Properties, JSON config) into a Map<String, Object> before assignment.
- 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
- Always declare OPTIONS as public static final Map<String, Object>.
- Keep option keys as constants from Library.OPTION_* to avoid other option-related failures.
- Add a reflection-based startup test validating the mapping class shape before native load.
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
- Unexpected registry type + lpType.getValue() + , expected RE
- Unexpected registry type + lpType.getValue() + , expected RE
- Unexpected registry type {}, expected REG_SZ
- Unexpected registry type {}, expected REG_BINARY
- Unexpected registry type {}, expected REG_DWORD
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/e3b879e030d29dcc.
Report an issue: GitHub.