java-native-access/jna · error · com.sun.jna.platform.win32.COM.COMException

ComObject must define a value for either clsId or progId

Error message

ComObject must define a value for either clsId or progId

What it means

ObjectFactory.discoverClsId resolves a GUID from the @ComObject annotation: if clsId is present it is parsed directly, else progId is converted via CLSIDFromProgID. If neither is set, this COMException is thrown. It means the annotation exists but is empty of identifying information.

Source

Thrown at contrib/platform/src/com/sun/jna/platform/win32/COM/util/ObjectFactory.java:168

        return t;
    }

    GUID discoverClsId(ComObject annotation) {
        assert COMUtils.comIsInitialized() : "COM not initialized";

        String clsIdStr = annotation.clsId();
        final String progIdStr = annotation.progId();
        if (null != clsIdStr && !clsIdStr.isEmpty()) {
            return new CLSID(clsIdStr);
        } else if (null != progIdStr && !progIdStr.isEmpty()) {
            final CLSID.ByReference rclsid = new CLSID.ByReference();

            WinNT.HRESULT hr = Ole32.INSTANCE.CLSIDFromProgID(progIdStr, rclsid);

            COMUtils.checkRC(hr);
            return rclsid;
        } else {
            throw new COMException("ComObject must define a value for either clsId or progId");
        }
    }

    IDispatchCallback createDispatchCallback(Class<?> comEventCallbackInterface, IComEventCallbackListener comEventCallbackListener) {
        return new CallbackProxy(this, comEventCallbackInterface, comEventCallbackListener);
    }

    // Proxy object release their COM interface reference latest in the
    // finalize method, which is run when garbadge collection removes the
    // object.
    // When the factory is finished, the referenced objects loose their
    // environment and can't be used anymore. registeredObjects is used
    // to dispose interfaces even if garbadge collection has not yet collected
    // the proxy objects.
    private final List<WeakReference<ProxyObject>> registeredObjects = new LinkedList<>();

    public void register(ProxyObject proxyObject) {
        synchronized (this.registeredObjects) {

View on GitHub (pinned to d036ad9781)

Solutions

  1. Set exactly one of @ComObject(clsId="{GUID}") or @ComObject(progId="App.Object")
  2. Prefer progId for readable code, or clsId for precision; verify the value exists on the target machine (progId resolution happens via CLSIDFromProgID)
  3. Validate the clsId GUID format if using clsId

Example fix

// before
@ComObject
public interface IWorksheet extends IUnknown { ... }
// after
@ComObject(clsId = "{00020820-0000-0000-C000-000000000046}")
public interface IWorksheet extends IUnknown { ... }
Defensive patterns

Strategy: validation

Validate before calling

ComObject a = iface.getAnnotation(ComObject.class);
if (a == null || (a.clsId().isEmpty() && a.progId().isEmpty()))
    throw new IllegalArgumentException(iface.getName() + " must set clsId or progId");

Try / catch

try { T obj = factory.createObject(iface); } catch (COMException e) { if (e.getMessage().contains("must define a value")) { /* set clsId/progId */ } throw e; }

Prevention

When it happens

Trigger: Declaring @ComObject with neither clsId nor progId attributes; annotation attributes left as empty strings after removing a placeholder; a helper passing a partially populated annotation instance.

Common situations: Copying annotation boilerplate and deleting both attribute values; generating interfaces from a tool that left the fields blank; typos in attribute names so neither recognized field is actually set.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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