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
- 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).
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
- 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).
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
- LookupAccountNameW was expected to fail with ERROR_INSUFFICI
- Expected GetTokenInformation to fail with ERROR_INSUFFICIENT
- Failed to find privilege "{privilege}" - {GetLastError}
- Win32Exception from native GetLastError after ExpandEnvironm
- Win32Exception from native GetLastError when GetPriorityClas
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/ee4840f42d8a5734.
Report an issue: GitHub.