I’d like to share my recent discovery about the user download folder. At first sight, it seems like a trivial question. But believe me, it’s not.
So buckle up, and join me for a journey in uncharted Windows registry waters..
What I have been doing for years, and that everyone is doing is this :
Paths.get(System.getProperty("user.home"), "Downloads");
Basically, we are targetting the downloads folder for the current user. This is simple and working. But did you know that you can actually change the default download folder on Windows? I learnt that because one of my client was doing it and an issue was raised.
The procedure is explained here : https://www.howtogeek.com/273618/how-do-you-change-windows-default-download-path/
I then tried to seek an environment variable or a Java variable allowing me to retrieving that new download path. But it was nowhere to be seen! I launched Chrome, and saw that it was able to retrieve that new path. How the hell was it doing?
Long story short, I plunged into Chrome source code to find that :
// Return a default path for downloads that is safe. // We just use 'Downloads' under DIR_USER_DOCUMENTS. Localizing // 'downloads' is not a good idea because Chrome's UI language // can be changed. bool GetUserDownloadsDirectorySafe(FilePath* result) { if (!GetUserDocumentsDirectory(result)) return false; *result = result->Append(L"Downloads"); return true; } // On Vista and higher, use the downloads known folder. Since it can be // relocated to point to a "dangerous" folder, callers should validate that the // returned path is not dangerous before using it. bool GetUserDownloadsDirectory(FilePath* result) { typedef HRESULT (WINAPI *GetKnownFolderPath)( REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*); GetKnownFolderPath f = reinterpret_cast<GetKnownFolderPath>( GetProcAddress(GetModuleHandle(L"shell32.dll"), "SHGetKnownFolderPath")); base::win::ScopedCoMem<wchar_t> path_buf; if (f && SUCCEEDED(f(FOLDERID_Downloads, 0, NULL, &path_buf))) { *result = FilePath(std::wstring(path_buf)); return true; } return GetUserDownloadsDirectorySafe(result); }
Apparently, sneaky Chrome is directly calling a method on shell32.dll. If we pull the string a little bit, we see that Chrome is calling SHGetKnownFolderPath function which use a KNOWNFOLDERID.
And if we scroll a little bit, what do we find?
So here it is! Our lord, our savior, I’m speaking of the world famous
{374DE290-123F-4565-9164-39C4925E467B}
guid in the registry! Joke aside, here are some snippets allowing you to retrieve the default download folder on Windows knowing all that :
import com.sun.javafx.PlatformUtil; import com.sun.jna.platform.win32.Advapi32Util; import com.sun.jna.platform.win32.WinReg; /** * Return the default folder for downloads of the current user by looking * into the registry key. * @return */ public static String getDefaultDownloadFolder() { try { if (PlatformUtil.isWindows()) { return Advapi32Util.registryGetStringValue(WinReg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", "{374DE290-123F-4565-9164-39C4925E467B}"); } else { return null; } } catch (Exception ex) { LOGGER.error("Problem when reading in registry", ex); } return null; }
My method is looking into the registry for the default download folder. If nothing is found, then we can fallback on the common solution described at the very top.
I know this is quite ugly, but frankly, I have not found better way to do so. Can we also call the windows dll directly with Java? And also, what about other platforms like Mac? If you have a better solution or anything, please comment and share.
Cheers,
Looks “ok” for Java 8, but what about Java 9+. This looks like private API…
Not sure if it’s all that much of a better way to do it, but I use “Runtime.getRuntime().exec()” with “reg query” and the same registry path/key you pointed to. This at least avoids need for JNA.