java-native-access/jna · error · IllegalArgumentException

Structures require bidirectional type conversion for " + typ

Error message

Structures require bidirectional type conversion for " + type

What it means

When a TypeMapper supplies only a one-way converter (toNative or fromNative) but not both, JNA cannot round-trip the field value between Java and native memory. Structures need bidirectional conversion, so IllegalArgumentException is thrown naming the type.

Source

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

                nativeType = tc.nativeType();
                structField.writeConverter = tc;
                structField.readConverter = tc;
                structField.context = new StructureReadContext(this, field);
            }
            else if (typeMapper != null) {
                ToNativeConverter writeConverter = typeMapper.getToNativeConverter(type);
                FromNativeConverter readConverter = typeMapper.getFromNativeConverter(type);
                if (writeConverter != null && readConverter != null) {
                    value = writeConverter.toNative(value,
                                                    new StructureWriteContext(this, structField.field));
                    nativeType = value != null ? value.getClass() : Pointer.class;
                    structField.writeConverter = writeConverter;
                    structField.readConverter = readConverter;
                    structField.context = new StructureReadContext(this, field);
                }
                else if (writeConverter != null || readConverter != null) {
                    String msg = "Structures require bidirectional type conversion for " + type;
                    throw new IllegalArgumentException(msg);
                }
            }

            if (value == null) {
                value = initializeField(structField.field, type);
            }

            try {
                structField.size = getNativeSize(nativeType, value);
                fieldAlignment = getNativeAlignment(nativeType, value, firstField);
            }
            catch(IllegalArgumentException e) {
                // Might simply not yet have a type mapper set yet
                if (!force && typeMapper == null) {
                    return null;
                }
                String msg = "Invalid Structure field in " + getClass() + ", field name '" + structField.name + "' (" + structField.type + "): " + e.getMessage();
                throw new IllegalArgumentException(msg, e);

View on GitHub (pinned to d036ad9781)

Solutions

  1. Implement a TypeMapper whose getToNativeConverter and getFromNativeConverter both return converters for the field type
  2. If the field is genuinely read-only in native terms, use a Pointer or raw type and read manually instead of a one-way mapper
  3. Verify the mapper's registry includes the exact Java type (and its subclasses) on both sides

Example fix

// before
mapper.getToNativeConverter(...); // only toNative registered
// after
class MyMapper implements TypeMapper {
    public ToNativeConverter getToNativeConverter(Class<?> c) { ... }
    public FromNativeConverter getFromNativeConverter(Class<?> c) { ... } // both directions
}
Defensive patterns

Strategy: validation

Validate before calling

TypeMapper m = ...;
for (Class<?> t : mappedFieldTypes) {
    if (m.getToNativeConverter(t) == null || m.getFromNativeConverter(t) == null) {
        throw new IllegalStateException("Converter not bidirectional for " + t);
    }
}

Try / catch

try {
    s.size();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("bidirectional")) {
        throw new IllegalStateException("Register both to/from converters for " + e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Setting a TypeMapper whose FromNativeConverter or ToNativeConverter covers a type but not the reverse direction, then calculating layout/writing a Structure field of that type.

Common situations: Custom mappers written for serialization only (toNative) and reused for structures; combining multiple mappers where one side's registry misses a type; third-party TypeMapper implementations that only implement one converter interface.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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