{"record":{"id":"ee4840f42d8a5734","repo":"java-native-access/jna","slug":"win32exception-from-native-getlasterror-after-ee4840","errorCode":null,"errorMessage":"Win32Exception from native GetLastError after ExpandEnvironmentStrings returned 0 (second call)","messagePattern":"Win32Exception from native GetLastError after ExpandEnvironmentStrings returned 0 \\(second call\\)","errorType":"error_code","errorClass":"Win32Exception","httpStatus":null,"severity":"error","filePath":"contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java","lineNumber":1251,"sourceCode":"        int resultChars = Kernel32.INSTANCE.ExpandEnvironmentStrings(input, null, 0);\n\n        if(resultChars == 0) {\n            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());\n        }\n\n        Memory resultMemory;\n        if( W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ) {\n            resultMemory = new Memory(resultChars * Native.WCHAR_SIZE);\n        } else {\n            // return value is length in chars including terminating NULL,\n            // documentation for ANSI version says: buffer size should be the\n            // string length, plus terminating null character, plus one\n            resultMemory = new Memory(resultChars + 1);\n        }\n        resultChars = Kernel32.INSTANCE.ExpandEnvironmentStrings(input, resultMemory, resultChars);\n\n        if(resultChars == 0) {\n            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());\n        }\n\n        if( W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ) {\n            return resultMemory.getWideString(0);\n        } else {\n            return resultMemory.getString(0);\n        }\n    }\n\n    /**\n     * Gets the priority class of the current process.\n     *\n     * @return The priority class of the current process.\n     * @throws Win32Exception if an error occurs.\n     */\n    public static DWORD getCurrentProcessPriority() {\n        final DWORD dwPriorityClass = Kernel32.INSTANCE.GetPriorityClass(Kernel32.INSTANCE.GetCurrentProcess());\n        if (!isValidPriorityClass(dwPriorityClass)) {","sourceCodeStart":1233,"sourceCodeEnd":1269,"githubUrl":"https://github.com/java-native-access/jna/blob/d036ad9781adad4b66693e8fa7098e4ac665e0a3/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java#L1233-L1269","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the expansion — the size query/expansion pair is inherently racy; a retry usually succeeds.","Inspect Win32Exception.getErrorCode() (e.g. ERROR_INSUFFICIENT_BUFFER = 122) to confirm the buffer race.","Avoid mutating process environment variables (e.g. via setenv/putenv or other threads) concurrently with expansion calls.","Ensure W32APITypeMapper settings are consistent so character counts match buffer allocation (WCHAR_SIZE vs byte)."],"exampleFix":"// before\nString expanded = Kernel32Util.expandEnvironmentStrings(path);\n// after\nString expanded;\ntry {\n    expanded = Kernel32Util.expandEnvironmentStrings(path);\n} catch (Win32Exception e) {\n    if (e.getErrorCode() == WinError.ERROR_INSUFFICIENT_BUFFER) {\n        expanded = Kernel32Util.expandEnvironmentStrings(path); // retry once\n    } else {\n        throw e;\n    }\n}","handlingStrategy":"retry","validationCode":"if (!Platform.isWindows()) {\n    throw new IllegalStateException(\"expandEnvironmentStrings is Windows-only\");\n}\nif (input.indexOf('%') == -1) {\n    return input; // nothing to expand, skip native call\n}","typeGuard":"boolean needsExpansion(String s) { return s != null && s.indexOf('%') != -1; }","tryCatchPattern":"int attempts = 0;\nwhile (true) {\n    try {\n        result = Kernel32Util.expandEnvironmentStrings(input);\n        break;\n    } catch (Win32Exception e) {\n        if (++attempts < 2) continue; // size/expansion race, retry once\n        throw e;\n    }\n}","preventionTips":["Do not mutate the process environment concurrently with expansion calls.","Retry once on failure — the two-call size/expansion sequence is inherently racy.","Skip the native call when the input contains no '%' characters.","Log the Win32 error code (ERROR_INSUFFICIENT_BUFFER=122 suggests a race)."],"tags":["win32","jna","windows","buffer","race-condition"],"backgroundTag":"api-error-response","analyzedSha":"d036ad9781adad4b66693e8fa7098e4ac665e0a3","analyzedAt":"2026-09-12T06:50:59.239Z","contentChangedAt":"2026-09-12T06:50:59.239Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}