java-native-access/jna · error · IllegalStateException

Null options field

Error message

Null options field

What it means

When initializing a library mapping, JNA reflectively reads the mapping class's public static OPTIONS field and casts it to Map. If the field exists and is accessible but its value is null, JNA throws IllegalStateException('Null options field') because a null options map cannot be defaulted silently on this path.

Solutions

  1. Initialize OPTIONS with a non-null map, e.g. Collections.emptyMap() if there are no options.
  2. Remove the OPTIONS field entirely if no options are needed (JNA then uses an empty map).
  3. Populate OPTIONS in a static initializer before the Native load call runs.

Example fix

// before
public interface MyLib extends Library {
    Map<String, Object> OPTIONS = null; // IllegalStateException
}
// after
public interface MyLib extends Library {
    Map<String, Object> OPTIONS = Collections.emptyMap();
}
Defensive patterns

Strategy: validation

Validate before calling

Object opts = mappingClass.getField("OPTIONS").get(null);
if (opts == null) {
    throw new IllegalStateException("OPTIONS must be initialized (use Collections.emptyMap(), not null)");
}

Type guard

boolean hasNonNullOptions(Class<?> c) throws Exception {
    try {
        return c.getField("OPTIONS").get(null) != null;
    } catch (NoSuchFieldException e) {
        return true; // absence is fine, JNA defaults to empty map
    }
}

Try / catch

try {
    lib = Native.load(name, mappingClass, options);
} catch (IllegalStateException e) {
    if ("Null options field".equals(e.getMessage())) {
        // fix mapping: initialize OPTIONS
    }
    throw e;
}

Prevention

When it happens

Trigger: Declaring 'public static Map<String, Object> OPTIONS;' (or assigning null) in a Library interface and letting Native read it during library setup — field is found via getField("OPTIONS") but its value is null.

Common situations: Copy-pasted OPTIONS declaration never initialized; refactoring that replaced the map with null; conditional initialization that didn't run before class load; OPTIONS = null left behind after removing options.

Related errors


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

Appendix: source

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

        Class<?> mappingClass = findEnclosingLibraryClass(type);
        if (mappingClass != null) {
            loadLibraryInstance(mappingClass);
        } else {
            mappingClass = type;
        }

        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);

View on GitHub (pinned to d036ad9781)