java-native-access/jna · error · Win32Exception

Win32Exception from native GetLastError after…

Error message

Win32Exception from native GetLastError after CreateToolhelp32Snapshot returned null

What it means

Kernel32Util.getModules throws this Win32Exception when Kernel32.INSTANCE.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID) returns null. GetLastError carries the native reason — commonly ERROR_ACCESS_DENIED (5) for protected processes, or ERROR_INVALID_PARAMETER (87) for a nonexistent process ID.

Solutions

  1. Validate the PID is alive before snapshotting (e.g. via Kernel32 OpenProcess or tasklist)
  2. Run the caller elevated (Administrator) when targeting processes from other sessions/users
  3. Catch Win32Exception and check errorCode: 5 = need elevation, 87 = invalid PID
  4. Retry once on transient snapshot failures if the PID is expected to exist

Example fix

// before
List<Tlhelp32.MODULEENTRY32W> mods = Kernel32Util.getModules(pid);
// after
try {
    List<Tlhelp32.MODULEENTRY32W> mods = Kernel32Util.getModules(pid);
} catch (Win32Exception e) {
    if (e.getErrorCode().intValue() == WinError.ERROR_ACCESS_DENIED) {
        throw new IllegalStateException("Run elevated to inspect PID " + pid, e);
    }
    throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Java: check PID liveness before snapshotting
HANDLE proc = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_LIMITED_INFORMATION, false, new DWORD(pid));
if (proc == null) throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); // PID dead or access denied
Kernel32.INSTANCE.CloseHandle(proc);

Type guard

boolean pidAlive(int pid) {
    HANDLE p = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_LIMITED_INFORMATION, false, new DWORD(pid));
    if (p == null) return false;
    Kernel32.INSTANCE.CloseHandle(p);
    return true;
}

Try / catch

try {
    List<Tlhelp32.MODULEENTRY32W> mods = Kernel32Util.getModules(pid);
} catch (Win32Exception e) {
    if (e.getErrorCode().intValue() == WinError.ERROR_ACCESS_DENIED) {
        throw new IllegalStateException("Elevation required for PID " + pid, e);
    }
    if (e.getErrorCode().intValue() == WinError.ERROR_INVALID_PARAMETER) {
        return Collections.emptyList(); // process gone
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling Kernel32Util.getModules(processID) with a PID that does not exist, a system/protected process (e.g. services running as SYSTEM, PPL processes), or insufficient privileges of the calling process for TH32CS_SNAPMODULE.

Common situations: Enumerating modules of another user's process without elevation, snapshotting a PID that already exited (race between lookup and snapshot), listing modules of antivirus/PPL-protected processes.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java:1172

        }

        if (err != null) {
            throw err;
        }
        return result;
    }

    /**
     * Returns all the executable modules for a given process ID.<br>
     *
     * @param processID
     *            The process ID to get executable modules for
     * @return All the modules in the process.
     */
    public static List<Tlhelp32.MODULEENTRY32W> getModules(int processID) {
        HANDLE snapshot = Kernel32.INSTANCE.CreateToolhelp32Snapshot(Tlhelp32.TH32CS_SNAPMODULE, new DWORD(processID));
        if (snapshot == null) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }

        Win32Exception we = null;
        try {
            Tlhelp32.MODULEENTRY32W first = new Tlhelp32.MODULEENTRY32W();

            if (!Kernel32.INSTANCE.Module32FirstW(snapshot, first)) {
                throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
            }

            List<Tlhelp32.MODULEENTRY32W> modules = new ArrayList<>();
            modules.add(first);

            Tlhelp32.MODULEENTRY32W next = new Tlhelp32.MODULEENTRY32W();
            while (Kernel32.INSTANCE.Module32NextW(snapshot, next)) {
                modules.add(next);
                next = new Tlhelp32.MODULEENTRY32W();
            }

View on GitHub (pinned to d036ad9781)