Is there a way in Java to determine if a path is valid without attempting to create a file?

JavaValidationFilesystems

Java Problem Overview


I need to determine if a user-supplied string is a valid file path (i.e., if createNewFile() will succeed or throw an Exception) but I don't want to bloat the file system with useless files, created just for validation purposes.

Is there a way to determine if the string I have is a valid file path without attempting to create the file?

I know the definition of "valid file path" varies depending on the OS, but I was wondering if there was any quick way of accepting C:/foo or /foo and rejecting banana.

A possible approach may be attempting to create the file and eventually deleting it if the creation succeeded, but I hope there is a more elegant way of achieving the same result.

Java Solutions


Solution 1 - Java

Path class introduced in Java 7 adds new alternatives, like the following:

/**
 * <pre>
 * Checks if a string is a valid path.
 * Null safe.
 * 	
 * Calling examples:
 *    isValidPath("c:/test");      //returns true
 *    isValidPath("c:/te:t");      //returns false
 *    isValidPath("c:/te?t");      //returns false
 *    isValidPath("c/te*t");       //returns false
 *    isValidPath("good.txt");     //returns true
 *    isValidPath("not|good.txt"); //returns false
 *    isValidPath("not:good.txt"); //returns false
 * </pre>
 */
public static boolean isValidPath(String path) {
	try {
		Paths.get(path);
	} catch (InvalidPathException | NullPointerException ex) {
		return false;
	}
	return true;
}

Edit:
Note Ferrybig's comment : "The only disallowed character in a file name on Linux is the NUL character, this does work under Linux."

Solution 2 - Java

This would check for the existance of the directory as well.

File file = new File("c:\\cygwin\\cygwin.bat");
if (!file.isDirectory())
   file = file.getParentFile();
if (file.exists()){
    ...
}

It seems like file.canWrite() does not give you a clear indication if you have permissions to write to the directory.

Solution 3 - Java

File.getCanonicalPath() is quite useful for this purpose. IO exceptions are thrown for certain types of invalid filenames (e.g. CON, PRN, *?* in Windows) when resolving against the OS or file system. However, this only serves as a preliminary check; you will still need to handle other failures when actually creating the file (e.g. insufficient permissions, lack of drive space, security restrictions).

Solution 4 - Java

A number of things can go wrong when you try and create a file:

  • Your lack the requisite permissions;
  • There is not enough space on the device;
  • The device experiences an error;
  • Some policy of custom security prohibits you from creating a file of a particular type;
  • etc.

More to the point, those can change between when you try and query to see if you can and when you actually can. In a multithreaded environment this is one of the primary causes of race conditions and can be a real vulnerability of some programs.

Basically you just have to try and create it and see if it works. And that's the correct way to do it. It's why things like ConcurrentHashMap has a putIfAbsent() so the check and insert is an atomic operation and doesn't suffer from race conditions. Exactly the same principle is in play here.

If this is just part of some diagnostic or install process, just do it and see if it works. Again there's no guarantee that it'll work later however.

Basically your program has to be robust enough to die gracefully if it can't write a relevant file.

Solution 5 - Java

boolean canWrite(File file) {
  if (file.exists()) {
    return file.canWrite();
  }
  else {
    try {
      file.createNewFile();
      file.delete();
      return true;
    }
    catch (Exception e) {
      return false;
    }
  }
}

Solution 6 - Java

Here's something you can do that works across operating systems

Using regex match to check for existing known invalid characters.

if (newName.matches(".*[/\n\r\t\0\f`?*\\<>|\":].*")) {
	System.out.println("Invalid!");
} else {
	System.out.println("Valid!");
}

Pros

  • This works across operating systems
  • You can customize it whatever way you want by editing that regex.

Cons

  • This might not be a complete list and need more research to fill in more invalid patterns or characters.

Solution 7 - Java

Just do it (and clean up after yourself)

>A possible approach may be attempting to create the file and eventually deleting it if the creation succeeded, but I hope there is a more elegant way of achieving the same result.

Maybe that's the most robust way.

Below is canCreateOrIsWritable that determines whether your program is able to create a file and its parent directories at a given path, or, if there's already a file there, write to it.

It does so by actually creating the necessary parent directories as well as an empty file at the path. Afterwards, it deletes them (if there existed a file at the path, it's left alone).

Here's how you might use it:

var myFile = new File("/home/me/maybe/write/here.log")

if (canCreateOrIsWritable(myFile)) {
    // We're good. Create the file or append to it
    createParents(myFile);
    appendOrCreate(myFile, "new content");
} else {
    // Let's pick another destination. Maybe the OS's temporary directory:
    var tempDir = System.getProperty("java.io.tmpdir");
    var alternative = Paths.get(tempDir, "second_choice.log");
    appendOrCreate(alternative, "new content in temporary directory");
}

The essential method with a few helper methods:

static boolean canCreateOrIsWritable(File file) {
    boolean canCreateOrIsWritable;

    // The non-existent ancestor directories of the file.
    // The file's parent directory is first
    List<File> parentDirsToCreate = getParentDirsToCreate(file);

    // Create the parent directories that don't exist, starting with the one
    // highest up in the file system hierarchy (closest to root, farthest
    // away from the file)
    reverse(parentDirsToCreate).forEach(File::mkdir);

    try {
        boolean wasCreated = file.createNewFile();
        if (wasCreated) {
            canCreateOrIsWritable = true;
            // Remove the file and its parent dirs that didn't exist before
            file.delete();
            parentDirsToCreate.forEach(File::delete);
        } else {
            // There was already a file at the path → Let's see if we can
            // write to it
            canCreateOrIsWritable = java.nio.file.Files.isWritable(file.toPath());
        }
    } catch (IOException e) {
        // File creation failed
        canCreateOrIsWritable = false;
    }
    return canCreateOrIsWritable;
}

static List<File> getParentDirsToCreate(File file) {
    var parentsToCreate = new ArrayList<File>();
    File parent = file.getParentFile();
    while (parent != null && !parent.exists()) {
        parentsToCreate.add(parent);

        parent = parent.getParentFile();
    }
    return parentsToCreate;
}

static <T> List<T> reverse(List<T> input) {
    var reversed = new ArrayList<T>();
    for (int i = input.size() - 1; i >= 0; i--) {
        reversed.add(input.get(i));
    }
    return reversed;
}

static void createParents(File file) {
    File parent = file.getParentFile();
    if (parent != null) {
        parent.mkdirs();
    }
}

Keep in mind that between calling canCreateOrIsWritable and creating the actual file, the contents and permissions of your file system might have changed.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRaibazView Question on Stackoverflow
Solution 1 - Javac0derView Answer on Stackoverflow
Solution 2 - JavakrosenvoldView Answer on Stackoverflow
Solution 3 - JavaZach ScrivenaView Answer on Stackoverflow
Solution 4 - JavacletusView Answer on Stackoverflow
Solution 5 - Javasullivan-View Answer on Stackoverflow
Solution 6 - JavaFangmingView Answer on Stackoverflow
Solution 7 - JavaMatthias BraunView Answer on Stackoverflow