What I need is a platform-independent way of obtaining the path to the local application data directory. System.getenv("LOCALAPPDATA")
seems to work only with Windows. How do I go about this?
You could probably say something like (contradict me if I am wrong, or if this a bad approach)
private String workingDirectory;
//here, we assign the name of the OS, according to Java, to a variable...
private String OS = (System.getProperty("os.name")).toUpperCase();
//to determine what the workingDirectory is.
//if it is some version of Windows
if (OS.contains("WIN"))
{
//it is simply the location of the "AppData" folder
workingDirectory = System.getenv("AppData");
}
//Otherwise, we assume Linux or Mac
else
{
//in either case, we would start in the user's home directory
workingDirectory = System.getProperty("user.home");
//if we are on a Mac, we are not done, we look for "Application Support"
workingDirectory += "/Library/Application Support";
}
//we are now free to set the workingDirectory to the subdirectory that is our
//folder.
Note that, in this code, I am taking full advantage that Java treats '/'
the same as '\\'
when dealing with directories. Windows uses '\\'
as pathSeparator, but it is happy with '/'
, too. (At least Windows 7 is.) It is also case-insensitive on it's environment variables; we could have just as easily said workingDirectory = System.getenv("APPDATA");
and it would have worked just as well.
private final String workingDirectory = System.getenv("AppData") + File.separatorChar + ourFolder;
and then private File dataFolder = new File("workingDirectory");
System.out.println("Our dataFolder was " + ((dataFolder.mkdir()) ? "" : "not ") + "created successfully");
does NOT guarantee that the parent directory of dataFolder
will be the AppData
folder. //In my case, this was stored in Roaming
. –
Savory "user.home"
is a valid property on that OS; my experience with Linux doesn't include playing around on there. //Happy coding, and you might want to accept an answer.
–
Savory SecurityException
on some machines: docs.oracle.com/javase/tutorial/essential/environment/… –
Savory Application Support
folder. E.g. your code would fail for sandboxed applications. –
Strother System.getProperty("user.home");
that would probably be the folder to be directly used by the app, right? –
Savory "user.home"
property return /home/username
as Windows does. And as a tip, use a directory that start with a dot if you wanna hide it as user.home
is the user folder and normally program folder are hide (start with a dot). –
Syncretize workingDirectory = System.getenv("AppData")
and then test if it's null to change it to UNIX location? –
Petrology For example, whereas a non-sandboxed app might store files in ~/Library/Application Support/<bundle_id>, the sandboxed version of the same app does not have access to that directory
. –
Strother Personally, I found appdirs
to be very helpful for similar use-cases. It has functions that locate different kinds of useful directories:
getUserDataDir
getUserConfigDir
getUserCacheDir
getUserLogDir
getSiteDataDir
← looks like this is the one you needgetSiteConfigDir
The locations it returns are more or less standard:
- On Unix it follows XDG Base Directory Specification.
- On Windows it invokes SHGetFolderPath.
- On macOS it uses well-known hardcoded paths.
The questing is old but I am missing an answer listing environment vars instead of some funny absolute paths. I do not know anything about OSX. this post only contains information about windows and linux.
I have not enough points to extend an already existing answer so I have to write a new one.
Linux: As previously mentioned there exists something like freedesktop.org which is defining a standard the linux distributions are trying to fulfill. There is also a subpage defining environment variables and their default values (If they are not set they are empty by default. The application has to match the variable to the default). Link to that page: freedesktop.org env vars
Vars defined relevant for this question:
- $XDG_DATA_HOME (local) (defaults to: $HOME/.local/share)
- $XDG_CONFIG_HOME (local) (defaults to: $HOME/.config)
- $XDG_DATA_DIRS (global) (defaults to: /usr/local/share/ or /usr/share/)
- $XDG_CONFIG_DIRS (global) (defaults to: /etc/xdg)
Windows XP:
%APPDATA% (defaults to: C:\Documents and Settings{username}\Application Data)
%CommonProgramFiles% (defaults to: C:\Program Files\Common Files) (shared program files)
%CommonProgramFiles(x86)% (defaults to: C:\Program Files (x86)\Common Files) (64-bit only!) (shared program files)
%ProgramFiles% (defaults to: %SystemDrive%\Program Files)
%ProgramFiles(x86)% (defaults to: %SystemDrive%\Program Files (x86) (only in 64-bit version)) (64-bit only!)
Windows Vista +:
- %APPDATA% (defaults to: C:\Users{username}\AppData\Roaming) (Shared between linked workstations. User local. Save files and configs)
- %LOCALAPPDATA% (defaults to: C:\Users{username}\AppData\Local) (User local. Save files and configs)
- %CommonProgramFiles% (defaults to: C:\Program Files\Common Files) (shared program files)
%CommonProgramFiles(x86)% (defaults to: C:\Program Files (x86)\Common Files) (64-bit only!) (shared program files)
%ProgramFiles% (defaults to: %SystemDrive%\Program Files) (Static data that will not change after installation)
%ProgramFiles(x86)% (defaults to: %SystemDrive%\Program Files (x86) (only in 64-bit version)) (64-bit only!) (Static data that will not change after installation)
%ProgramData% (defaults to: %SystemDrive%\ProgramData) (Changeable data affecting all users)
In short: Linux has two environment variables which might not be set (one for configs, one for files). Windows has as far as I can tell only one environment var for configs and files together. Please use these instead of absolute paths.
For moderate amounts of data, consider java.util.prefs.Preferences
, mentioned here. It is cross-platform.
An erstwhile alternative, javax.jnlp.PersistenceService
, discussed here, is now deprecated.
java.util.Preferences
? –
Underestimate Preferences
precludes the need to specify a workingDirectory
, as Preferences
already abstracts the location. –
Minimum There is no cross platform way for that, because the concepts that the different OS-s use are too different to "abstract away". I am not familiar with *nix and Mac conventions, but on Windows there is no "home folder" and the application has to specify whether it wants to store stuff in the roaming profile (C:\Users\<username>\AppData\Roaming\<application vendor>\<application name>\
by default) or the local profile (C:\Users\<username>\AppData\Local\<application vendor>\<application name>\
by default).
Note that you cannot hardcode these paths, because on a networked installation they might be somewhere else. You shouldn't rely on environment variables either because they can be modified by the user. Your application should call the SHGetKnownFolderPath function of the Windows API.
The difference between the two is that the local profile is specific to the user and the machine, while the roaming profile is specific to the user, so in a setup like my university, stuff apps put in the roaming profile are uploaded to the server and synced to whichever computer I log in to.
It should be the responsibility of applications to choose whether the settings they want to store are local or roaming. Unfortunately Java does not allow apps to decide this. Instead there is a global user-configurable setting that determines which folder you'll get.
This is a culmination of various other StackOverflow answers alongside some ideas from the appdata project, without introducing a dependency on JNA. There is a dependency on Apache Commons Lang3, which can be eliminated by using return values from System.getProperty( "os.name" )
as shown elsewhere.
Code
Note that I haven't tested this on any platform except Linux:
import java.io.FileNotFoundException;
import java.nio.file.Path;
import static java.lang.System.getProperty;
import static java.lang.System.getenv;
import static org.apache.commons.lang3.SystemUtils.*;
/**
* Responsible for determining the directory to write application data, across
* multiple platforms. See also:
*
* <ul>
* <li>
* <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">
* Linux: XDG Base Directory Specification
* </a>
* </li>
* <li>
* <a href="https://learn.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables">
* Windows: Recognized environment variables
* </a>
* </li>
* <li>
* <a href="https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html">
* MacOS: File System Programming Guide
* </a>
* </li>
* </ul>
* </p>
*/
public final class UserDataDir {
private static final Path UNDEFINED = Path.of( "/" );
private static final String PROP_USER_HOME = getProperty( "user.home" );
private static final String PROP_OS_VERSION = getProperty( "os.version" );
private static final String ENV_APPDATA = getenv( "AppData" );
private static final String ENV_XDG_DATA_HOME = getenv( "XDG_DATA_HOME" );
private UserDataDir() { }
public static Path getAppPath( final String appName )
throws FileNotFoundException {
final var osPath = isWindows()
? getWinAppPath()
: isMacOs()
? getMacAppPath()
: isUnix()
? getUnixAppPath()
: UNDEFINED;
final var path = osPath.equals( UNDEFINED )
? getDefaultAppPath( appName )
: osPath.resolve( appName );
return ensureExists( path ) ? path : fail( path );
}
private static Path fail( final Path path ) throws FileNotFoundException {
throw new FileNotFoundException( path.toString() );
}
private static Path getWinAppPath() {
return ENV_APPDATA == null || ENV_APPDATA.isBlank()
? home( getWinVerAppPath() )
: Path.of( ENV_APPDATA );
}
/**
* Gets the application path with respect to the Windows version.
*
* @return The directory name paths relative to the user's home directory.
*/
private static String[] getWinVerAppPath() {
return PROP_OS_VERSION.startsWith( "5." )
? new String[]{"Application Data"}
: new String[]{"AppData", "Roaming"};
}
private static Path getMacAppPath() {
final var path = home( "Library", "Application Support" );
return ensureExists( path ) ? path : UNDEFINED;
}
private static Path getUnixAppPath() {
// Fallback in case the XDG data directory is undefined.
var path = home( ".local", "share" );
if( ENV_XDG_DATA_HOME != null && !ENV_XDG_DATA_HOME.isBlank() ) {
final var xdgPath = Path.of( ENV_XDG_DATA_HOME );
path = ensureExists( xdgPath ) ? xdgPath : path;
}
return path;
}
/**
* Returns a hidden directory relative to the user's home directory.
*
* @param appName The application name.
* @return A suitable directory for storing application files.
*/
private static Path getDefaultAppPath( final String appName ) {
return home( '.' + appName );
}
private static Path home( final String... paths ) {
return Path.of( PROP_USER_HOME, paths );
}
/**
* Verifies whether the path exists or was created.
*
* @param path The directory to verify.
* @return {@code true} if the path already exists or was created,
* {@code false} if the directory doesn't exist and couldn't be created.
*/
private static boolean ensureExists( final Path path ) {
final var file = path.toFile();
return file.exists() || file.mkdirs();
}
private static boolean isWindows() {
return IS_OS_WINDOWS;
}
private static boolean isMacOs() {
return IS_OS_MAC;
}
private static boolean isUnix() {
return IS_OS_UNIX;
}
}
Test
A unit test to show usage:
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import static org.junit.jupiter.api.Assertions.*;
class UserDataDirTest {
@Test
void test_Unix_GetAppDirectory_DirectoryExists()
throws FileNotFoundException {
final var path = UserDataDir.getAppPath( "test" );
final var file = path.toFile();
assertTrue( file.exists() );
assertTrue( file.delete() );
assertFalse( file.exists() );
}
}
You can use this
String currentDir = new File(".").getAbsolutePath();
or this:
System.getProperty("user.dir")
I prefer the first option
Regards
© 2022 - 2024 — McMap. All rights reserved.