java-native-access/jna · error · Win32Exception

Win32Exception from native GetLastError after ExpandEnvironm

Error message

Win32Exception from native GetLastError after ExpandEnvironmentStrings returned 0 (second call)

What it means

After the size query succeeds, expandEnvironmentStrings() calls ExpandEnvironmentStrings a second time with an allocated buffer. If this second call returns 0, the expansion failed and the library throws a Win32Exception built from native GetLastError(). This typically means the buffer was insufficient or the environment changed between the two calls.

Source

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

        int resultChars = Kernel32.INSTANCE.ExpandEnvironmentStrings(input, null, 0);

        if(resultChars == 0) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }

        Memory resultMemory;
        if( W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ) {
            resultMemory = new Memory(resultChars * Native.WCHAR_SIZE);
        } else {
            // return value is length in chars including terminating NULL,
            // documentation for ANSI version says: buffer size should be the
            // string length, plus terminating null character, plus one
            resultMemory = new Memory(resultChars + 1);
        }
        resultChars = Kernel32.INSTANCE.ExpandEnvironmentStrings(input, resultMemory, resultChars);

        if(resultChars == 0) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }

        if( W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ) {
            return resultMemory.getWideString(0);
        } else {
            return resultMemory.getString(0);
        }
    }

    /**
     * Gets the priority class of the current process.
     *
     * @return The priority class of the current process.
     * @throws Win32Exception if an error occurs.
     */
    public static DWORD getCurrentProcessPriority() {
        final DWORD dwPriorityClass = Kernel32.INSTANCE.GetPriorityClass(Kernel32.INSTANCE.GetCurrentProcess());
        if (!isValidPriorityClass(dwPriorityClass)) {

View on GitHub (pinned to d036ad9781)

Solutions

  1. Retry the expansion — the size query/expansion pair is inherently racy; a retry usually succeeds.
  2. Inspect Win32Exception.getErrorCode() (e.g. ERROR_INSUFFICIENT_BUFFER = 122) to confirm the buffer race.
  3. Avoid mutating process environment variables (e.g. via setenv/putenv or other threads) concurrently with expansion calls.
  4. Ensure W32APITypeMapper settings are consistent so character counts match buffer allocation (WCHAR_SIZE vs byte).

Example fix

// before
String expanded = Kernel32Util.expandEnvironmentStrings(path);
// after
String expanded;
try {
    expanded = Kernel32Util.expandEnvironmentStrings(path);
} catch (Win32Exception e) {
    if (e.getErrorCode() == WinError.ERROR_INSUFFICIENT_BUFFER) {
        expanded = Kernel32Util.expandEnvironmentStrings(path); // retry once
    } else {
        throw e;
    }
}
Defensive patterns

Strategy: retry

Validate before calling

if (!Platform.isWindows()) {
    throw new IllegalStateException("expandEnvironmentStrings is Windows-only");
}
if (input.indexOf('%') == -1) {
    return input; // nothing to expand, skip native call
}

Type guard

boolean needsExpansion(String s) { return s != null && s.indexOf('%') != -1; }

Try / catch

int attempts = 0;
while (true) {
    try {
        result = Kernel32Util.expandEnvironmentStrings(input);
        break;
    } catch (Win32Exception e) {
        if (++attempts < 2) continue; // size/expansion race, retry once
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling Kernel32Util.expandEnvironmentStrings(String) where the first size-query succeeds but the second (actual expansion) call fails — e.g. the environment block was modified concurrently so the string no longer fits the pre-allocated buffer, or the buffer size returned in characters was misapplied for the ANSI code path.

Common situations: Applications expanding %VAR%-containing paths while another thread modifies the process environment between the two native calls; rare race conditions; non-UNICODE (ANSI) mappings with strings whose expanded size changed between calls.

Related errors


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