Get the user download folder path

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,

2 thoughts on “Get the user download folder path”

  1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *