java-native-access/jna · error · java.lang.IllegalArgumentException

OPTIONS must be a public field of type java.util.Map (e): ma

Error message

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

What it means

When JNA initializes a library mapping it reads the optional static OPTIONS field of the library interface via Class.getField and a cast to Map<String,Object>. If the field exists but is not public/static, not of type Map, is non-null-inaccessible, or throws during access, JNA wraps the failure in this IllegalArgumentException. It means the developer declared an OPTIONS field that does not match the contract documented in Library.

Source

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

        }

        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. Change the OPTIONS field to be public static final java.util.Map<String,Object>, e.g. Map OPTIONS = new HashMap();
  2. If no options are needed, delete the field entirely (JNA treats a missing OPTIONS as empty options)
  3. Ensure any static initializer populating OPTIONS cannot throw
  4. Verify the field is not shadowed with a wrong type in a subclass/nested interface

Example fix

// before
private static String OPTIONS = "advanced";
// after
public static final Map<String, Object> OPTIONS = new HashMap<String, Object>() {{ put(Library.OPTION_CALLING_CONVENTION, Function.C_CONVENTION); }};
Defensive patterns

Strategy: validation

Validate before calling

static void validateOptions(Class<?> lib) throws Exception {
    try {
        Field f = lib.getField("OPTIONS");
        if (!java.lang.reflect.Modifier.isStatic(f.getModifiers())) throw new IllegalStateException("OPTIONS not static");
        if (!Map.class.isAssignableFrom(f.getType())) throw new IllegalStateException("OPTIONS not a Map");
        if (f.get(null) == null) throw new IllegalStateException("OPTIONS is null");
    } catch (NoSuchFieldException ok) { /* fine: no options */ }
}

Type guard

boolean hasValidOptions(Class<?> c) {
    try { Field f = c.getField("OPTIONS"); return Map.class.isAssignableFrom(f.getType()) && f.get(null) != null; }
    catch (Exception e) { return false; }
}

Try / catch

try { Native.load("mylib", MyLib.class); } catch (IllegalArgumentException e) { if (String.valueOf(e.getMessage()).contains("OPTIONS must be a public field")) { throw new IllegalStateException("Fix MyLib.OPTIONS declaration", e); } throw e; }

Prevention

When it happens

Trigger: Calling Native.loadLibrary/Native.load on an interface that declares a static OPTIONS field whose type is not java.util.Map, is not public, is an instance (non-static) field, is initialized to a non-Map value, or whose initializer throws.

Common situations: Typo'd generics or wrong type like public static String OPTIONS; forgetting 'static'; making OPTIONS private/protected; an OPTIONS initializer that throws ExceptionInInitializerError; copy-pasting a CONSTANTS-style field pattern incorrectly.

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/210a36f4adcf2683. Report an issue: GitHub.