java-native-access/jna · error · Win32Exception
Win32Exception from native GetLastError on retry of GetVolum
Error message
Win32Exception from native GetLastError on retry of GetVolumePathNamesForVolumeName
What it means
This is the retry branch of getVolumePathNamesForVolumeName: after the first call fails with ERROR_MORE_DATA, the wrapper reallocates the buffer to lpcchReturnLength characters and calls the API again, expecting guaranteed success. If the retry still fails, Win32Exception is thrown with the latest GetLastError code. In practice this indicates the volume state changed between calls (return length shrank/invalidated) or a genuine access error surfaced on the second attempt.
Source
Thrown at contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java:877
* @param lpszVolumeName The volume name
* @return The parsed result
* @throws Win32Exception If failed to retrieve the required information
*/
public static final List<String> getVolumePathNamesForVolumeName(String lpszVolumeName) {
char[] lpszVolumePathNames = new char[WinDef.MAX_PATH + 1];
IntByReference lpcchReturnLength = new IntByReference();
if (!Kernel32.INSTANCE.GetVolumePathNamesForVolumeName(lpszVolumeName, lpszVolumePathNames, lpszVolumePathNames.length, lpcchReturnLength)) {
int hr = Kernel32.INSTANCE.GetLastError();
if (hr != WinError.ERROR_MORE_DATA) {
throw new Win32Exception(hr);
}
int required = lpcchReturnLength.getValue();
lpszVolumePathNames = new char[required];
// this time we MUST succeed
if (!Kernel32.INSTANCE.GetVolumePathNamesForVolumeName(lpszVolumeName, lpszVolumePathNames, lpszVolumePathNames.length, lpcchReturnLength)) {
throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
}
}
int bufSize = lpcchReturnLength.getValue();
return Native.toStringList(lpszVolumePathNames, 0, bufSize);
}
// prefix and suffix of a volume GUID path
public static final String VOLUME_GUID_PATH_PREFIX = "\\\\?\\Volume{";
public static final String VOLUME_GUID_PATH_SUFFIX = "}\\";
/**
* Parses and returns the pure GUID value of a volume name obtained
* from {@link Kernel32#FindFirstVolume(char[], int)} or
* {@link Kernel32#FindNextVolume} calls
*
* @param volumeGUIDPath
* The volume GUID path as returned by one of the above mentioned callsView on GitHub (pinned to d036ad9781)
Solutions
- Retry the whole two-call sequence once more — transient race with volume unmount is the usual cause.
- Re-enumerate the volume GUID freshly before retrying; the old GUID may be stale.
- Catch Win32Exception and fall back to enumerating all volumes (FindFirstVolume) to rebuild current state.
- If reproducible, log the error code: ERROR_ACCESS_DENIED points to permissions, not a race.
Example fix
// before
String[] paths = Kernel32Util.getVolumePathNamesForVolumeName(guid); // throws on transient race
// after
String[] paths = null;
for (int attempt = 0; attempt < 2 && paths == null; attempt++) {
try {
paths = Kernel32Util.getVolumePathNamesForVolumeName(guid);
} catch (Win32Exception e) {
if (attempt == 1) throw e;
Thread.sleep(50); // volume state may settle
}
} Defensive patterns
Strategy: retry
Validate before calling
String freshGuid = Kernel32Util.getVolumeNameForVolumeMountPoint(mountPoint); // refresh before use
Try / catch
for (int i = 0; i < 3; i++) {
try {
return Kernel32Util.getVolumePathNamesForVolumeName(guid);
} catch (Win32Exception e) {
if (i == 2) throw e;
Thread.sleep(50);
}
} Prevention
- Expect transient races with hot-pluggable volumes and wrap calls in bounded retries.
- Refresh volume GUIDs from the OS before retrying.
- Distinguish ERROR_ACCESS_DENIED (permissions) from race conditions via error code.
- Avoid unmounting/enumerating volumes concurrently from multiple threads.
When it happens
Trigger: Second GetVolumePathNamesForVolumeName call (after ERROR_MORE_DATA resize) returns FALSE — the volume was unmounted between calls, the reported required length became stale, or access was denied on retry.
Common situations: USB/network volumes being unmounted concurrently by another process or user; hot-plug device removal while enumerating mounts; race conditions in scripts that mount/unmount drives; rarely, antivirus interference on the volume query path.
Related errors
- Win32Exception from GetLastError after GetVolumePathNamesFor
- Bad volume GUID path format:
- LookupAccountNameW was expected to fail with ERROR_INSUFFICI
- Expected GetTokenInformation to fail with ERROR_INSUFFICIENT
- Failed to find privilege "{privilege}" - {GetLastError}
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/77b07e9948d6827a.
Report an issue: GitHub.