322

Given two absolute paths, e.g.

/var/data/stuff/xyz.dat
/var/data

How can one create a relative path that uses the second path as its base? In the example above, the result should be: ./stuff/xyz.dat

Mikaël Mayer
10.8k7 gold badges67 silver badges106 bronze badges
asked Oct 15, 2008 at 13:53
2
  • 3
    For Java 7 and later, see @VitaliiFedorenko's answer. Commented Jun 13, 2016 at 1:18
  • 3
    tl;dr answer: Paths.get(startPath).relativize(Paths.get(endPath)).toString() (which, by the way, seems to be working just fine with e.g. "../" for me in Java 8, so...) Commented Jul 13, 2017 at 13:44

23 Answers 23

336

It's a little roundabout, but why not use URI? It has a relativize method which does all the necessary checks for you.

String path = "/var/data/stuff/xyz.dat";
String base = "/var/data";
String relative = new File(base).toURI().relativize(new File(path).toURI()).getPath();
// relative == "stuff/xyz.dat"

Please note that for file path there's java.nio.file.Path#relativize since Java 1.7, as pointed out by @Jirka Meluzin in the other answer.

Marc Bannout
4511 gold badge6 silver badges17 bronze badges
answered Oct 15, 2008 at 17:32

5 Comments

See Peter Mueller's answer. relativize() appears pretty broken for all but the simplest cases.
Yep, it only works if the base path is a parent of the first path. If you need some hierarchical backward like "../../relativepath", it won't work. I found a solution: mrpmorris.blogspot.com/2007/05/…
As @VitaliiFedorenko wrote: use java.nio.file.Path#relativize(Path), it just works with parent double-dots and all.
Consider using toPath() instead of toURI(). It's perfectly able to create stuff like "..\..". But be aware of java.lang.IllegalArgumentException: 'other' has different root exception when asking for relative path from "C:\temp" to "D:\temp".
This does not work as expected, it returns data/stuff/xyz.dat in my test case.
288

Since Java 7 you can use the relativize method:

import java.nio.file.Path;
import java.nio.file.Paths;
public class Test {
 public static void main(String[] args) {
 Path pathAbsolute = Paths.get("/var/data/stuff/xyz.dat");
 Path pathBase = Paths.get("/var/data");
 Path pathRelative = pathBase.relativize(pathAbsolute);
 System.out.println(pathRelative);
 }
}

Output:

stuff/xyz.dat
answered Dec 18, 2011 at 23:17

7 Comments

Nice, short, no extra lib +1. Adam Crume's solution (hit 1) doesnt pass my tests and next answer (hit2) "The Only 'Working' Solution" adds a new jar AND is more code than my implementation, I find this here afterwards... better than never .-)
But watch out for this problem.
Checked that this handles adding .. where necessary (it does).
Unfortunately, Android doesn't include java.nio.file :(
I've found you get strange results if the "pathBase" is not "normalized" prior to "relativize". Although fine in this example, I would do pathBase.normalize().relativize(pathAbsolute); as a general rule.
|
78

At the time of writing (June 2010), this was the only solution that passed my test cases. I can't guarantee that this solution is bug-free, but it does pass the included test cases. The method and tests I've written depend on the FilenameUtils class from Apache commons IO.

The solution was tested with Java 1.4. If you're using Java 1.5 (or higher) you should consider replacing StringBuffer with StringBuilder (if you're still using Java 1.4 you should consider a change of employer instead).

import java.io.File;
import java.util.regex.Pattern;
import org.apache.commons.io.FilenameUtils;
public class ResourceUtils {
 /**
 * Get the relative path from one file to another, specifying the directory separator. 
 * If one of the provided resources does not exist, it is assumed to be a file unless it ends with '/' or
 * '\'.
 * 
 * @param targetPath targetPath is calculated to this file
 * @param basePath basePath is calculated from this file
 * @param pathSeparator directory separator. The platform default is not assumed so that we can test Unix behaviour when running on Windows (for example)
 * @return
 */
 public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {
 // Normalize the paths
 String normalizedTargetPath = FilenameUtils.normalizeNoEndSeparator(targetPath);
 String normalizedBasePath = FilenameUtils.normalizeNoEndSeparator(basePath);
 // Undo the changes to the separators made by normalization
 if (pathSeparator.equals("/")) {
 normalizedTargetPath = FilenameUtils.separatorsToUnix(normalizedTargetPath);
 normalizedBasePath = FilenameUtils.separatorsToUnix(normalizedBasePath);
 } else if (pathSeparator.equals("\\")) {
 normalizedTargetPath = FilenameUtils.separatorsToWindows(normalizedTargetPath);
 normalizedBasePath = FilenameUtils.separatorsToWindows(normalizedBasePath);
 } else {
 throw new IllegalArgumentException("Unrecognised dir separator '" + pathSeparator + "'");
 }
 String[] base = normalizedBasePath.split(Pattern.quote(pathSeparator));
 String[] target = normalizedTargetPath.split(Pattern.quote(pathSeparator));
 // First get all the common elements. Store them as a string,
 // and also count how many of them there are.
 StringBuffer common = new StringBuffer();
 int commonIndex = 0;
 while (commonIndex < target.length && commonIndex < base.length
 && target[commonIndex].equals(base[commonIndex])) {
 common.append(target[commonIndex] + pathSeparator);
 commonIndex++;
 }
 if (commonIndex == 0) {
 // No single common path element. This most
 // likely indicates differing drive letters, like C: and D:.
 // These paths cannot be relativized.
 throw new PathResolutionException("No common path element found for '" + normalizedTargetPath + "' and '" + normalizedBasePath
 + "'");
 } 
 // The number of directories we have to backtrack depends on whether the base is a file or a dir
 // For example, the relative path from
 //
 // /foo/bar/baz/gg/ff to /foo/bar/baz
 // 
 // ".." if ff is a file
 // "../.." if ff is a directory
 //
 // The following is a heuristic to figure out if the base refers to a file or dir. It's not perfect, because
 // the resource referred to by this path may not actually exist, but it's the best I can do
 boolean baseIsFile = true;
 File baseResource = new File(normalizedBasePath);
 if (baseResource.exists()) {
 baseIsFile = baseResource.isFile();
 } else if (basePath.endsWith(pathSeparator)) {
 baseIsFile = false;
 }
 StringBuffer relative = new StringBuffer();
 if (base.length != commonIndex) {
 int numDirsUp = baseIsFile ? base.length - commonIndex - 1 : base.length - commonIndex;
 for (int i = 0; i < numDirsUp; i++) {
 relative.append(".." + pathSeparator);
 }
 }
 relative.append(normalizedTargetPath.substring(common.length()));
 return relative.toString();
 }
 static class PathResolutionException extends RuntimeException {
 PathResolutionException(String msg) {
 super(msg);
 }
 } 
}

The test cases that this passes are

public void testGetRelativePathsUnix() {
 assertEquals("stuff/xyz.dat", ResourceUtils.getRelativePath("/var/data/stuff/xyz.dat", "/var/data/", "/"));
 assertEquals("../../b/c", ResourceUtils.getRelativePath("/a/b/c", "/a/x/y/", "/"));
 assertEquals("../../b/c", ResourceUtils.getRelativePath("/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}
public void testGetRelativePathFileToFile() {
 String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
 String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";
 String relPath = ResourceUtils.getRelativePath(target, base, "\\");
 assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}
public void testGetRelativePathDirectoryToFile() {
 String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
 String base = "C:\\Windows\\Speech\\Common\\";
 String relPath = ResourceUtils.getRelativePath(target, base, "\\");
 assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}
public void testGetRelativePathFileToDirectory() {
 String target = "C:\\Windows\\Boot\\Fonts";
 String base = "C:\\Windows\\Speech\\Common\\foo.txt";
 String relPath = ResourceUtils.getRelativePath(target, base, "\\");
 assertEquals("..\\..\\Boot\\Fonts", relPath);
}
public void testGetRelativePathDirectoryToDirectory() {
 String target = "C:\\Windows\\Boot\\";
 String base = "C:\\Windows\\Speech\\Common\\";
 String expected = "..\\..\\Boot";
 String relPath = ResourceUtils.getRelativePath(target, base, "\\");
 assertEquals(expected, relPath);
}
public void testGetRelativePathDifferentDriveLetters() {
 String target = "D:\\sources\\recovery\\RecEnv.exe";
 String base = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";
 try {
 ResourceUtils.getRelativePath(target, base, "\\");
 fail();
 } catch (PathResolutionException ex) {
 // expected exception
 }
}

2 Comments

Nice! One thing, though, it breaks if the base and target are the same - the string common is made to end in a separator, which the normalized target path doesn't have, so the substring call asks for one too many digits. Think I fixed it by adding the following before the last two lines of the function: if (common.length() >= normalizedTargetPath.length()) { return "."; }
Saying this is the only working solution is misleading. Other answers work better (this answer crashes when the base and target are the same), are simpler and don't rely on commons-io.
27

When using java.net.URI.relativize you should be aware of Java bug: JDK-6226081 (URI should be able to relativize paths with partial roots)

At the moment, the relativize() method of URI will only relativize URIs when one is a prefix of the other.

Which essentially means java.net.URI.relativize will not create ".."'s for you.

answered Apr 1, 2009 at 15:02

4 Comments

Nasty. There is a workaround for this, apparently: stackoverflow.com/questions/204784/…
Paths.get(startPath).relativize(Paths.get(endPath)).toString‌​() seems to be working just fine with e.g. "../" for me in Java 8.
@skaffman are you sure? This answer references bug JDK-6226081, but URIUtils.resolve() mentions JDK-4708535. And from the source code, I don't see anything related to backtracking (i.e. .. segments). Did you confuse the two bugs?
JDK-6920138 is marked as a duplicate of JDK-4708535.
27

In Java 7 and later you can simply use (and in contrast to URI, it is bug free):

Path#relativize(Path)
Mike Duigou
90110 silver badges19 bronze badges
answered Apr 7, 2016 at 13:34

Comments

16

The bug referred to in another answer is addressed by URIUtils in Apache HttpComponents

public static URI resolve(URI baseURI,
 String reference)

Resolves a URI reference against a base URI. Work-around for bug in java.net.URI ()

Zoe - Save the data dump
28.4k22 gold badges130 silver badges161 bronze badges
answered Aug 17, 2009 at 20:44

1 Comment

Doesn't the resolve method generate an absolute URI from a base and a relative path? How would this method help?
10

If you know the second string is part of the first:

String s1 = "/var/data/stuff/xyz.dat";
String s2 = "/var/data";
String s3 = s1.substring(s2.length());

or if you really want the period at the beginning as in your example:

String s3 = ".".concat(s1.substring(s2.length()));
answered Oct 15, 2008 at 14:09

1 Comment

String s3 = "." + s1.substring(s2.length()); is slightly more readable IMO
10

Recursion produces a smaller solution. This throws an exception if the result is impossible (e.g. different Windows disk) or impractical (root is only common directory.)

/**
 * Computes the path for a file relative to a given base, or fails if the only shared 
 * directory is the root and the absolute form is better.
 * 
 * @param base File that is the base for the result
 * @param name File to be "relativized"
 * @return the relative name
 * @throws IOException if files have no common sub-directories, i.e. at best share the
 * root prefix "/" or "C:\"
 */
public static String getRelativePath(File base, File name) throws IOException {
 File parent = base.getParentFile();
 if (parent == null) {
 throw new IOException("No common directory");
 }
 String bpath = base.getCanonicalPath();
 String fpath = name.getCanonicalPath();
 if (fpath.startsWith(bpath)) {
 return fpath.substring(bpath.length() + 1);
 } else {
 return (".." + File.separator + getRelativePath(parent, name));
 }
}
MikkoP
5,10217 gold badges61 silver badges108 bronze badges
answered Jun 26, 2012 at 20:11

1 Comment

getCanonicalPath can be heavy weight, so this solution can be not recommended when you need to process hundred thousand records. For example I have some listing files having up to million records and now I want to move them to use relative path for portability.
10

Here is a solution other library free:

Path sourceFile = Paths.get("some/common/path/example/a/b/c/f1.txt");
Path targetFile = Paths.get("some/common/path/example/d/e/f2.txt"); 
Path relativePath = sourceFile.relativize(targetFile);
System.out.println(relativePath);

Outputs

..\..\..\..\d\e\f2.txt

[EDIT] actually it outputs on more ..\ because of the source is file not a directory. Correct solution for my case is:

Path sourceFile = Paths.get(new File("some/common/path/example/a/b/c/f1.txt").parent());
Path targetFile = Paths.get("some/common/path/example/d/e/f2.txt"); 
Path relativePath = sourceFile.relativize(targetFile);
System.out.println(relativePath);
answered Sep 9, 2014 at 11:45

Comments

6

My version is loosely based on Matt and Steve's versions:

/**
 * Returns the path of one File relative to another.
 *
 * @param target the target directory
 * @param base the base directory
 * @return target's path relative to the base directory
 * @throws IOException if an error occurs while resolving the files' canonical names
 */
 public static File getRelativeFile(File target, File base) throws IOException
 {
 String[] baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));
 String[] targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));
 // skip common components
 int index = 0;
 for (; index < targetComponents.length && index < baseComponents.length; ++index)
 {
 if (!targetComponents[index].equals(baseComponents[index]))
 break;
 }
 StringBuilder result = new StringBuilder();
 if (index != baseComponents.length)
 {
 // backtrack to base directory
 for (int i = index; i < baseComponents.length; ++i)
 result.append(".." + File.separator);
 }
 for (; index < targetComponents.length; ++index)
 result.append(targetComponents[index] + File.separator);
 if (!target.getPath().endsWith("/") && !target.getPath().endsWith("\\"))
 {
 // remove final path separator
 result.delete(result.length() - File.separator.length(), result.length());
 }
 return new File(result.toString());
 }
answered Aug 13, 2009 at 3:38

1 Comment

+1 works for me. Only minor correction: instead of "/".length() you should use separator.length
4

Matt B's solution gets the number of directories to backtrack wrong -- it should be the length of the base path minus the number of common path elements, minus one (for the last path element, which is either a filename or a trailing "" generated by split). It happens to work with /a/b/c/ and /a/x/y/, but replace the arguments with /m/n/o/a/b/c/ and /m/n/o/a/x/y/ and you will see the problem.

Also, it needs an else break inside the first for loop, or it will mishandle paths that happen to have matching directory names, such as /a/b/c/d/ and /x/y/c/z -- the c is in the same slot in both arrays, but is not an actual match.

All these solutions lack the ability to handle paths that cannot be relativized to one another because they have incompatible roots, such as C:\foo\bar and D:\baz\quux. Probably only an issue on Windows, but worth noting.

I spent far longer on this than I intended, but that's okay. I actually needed this for work, so thank you to everyone who has chimed in, and I'm sure there will be corrections to this version too!

public static String getRelativePath(String targetPath, String basePath, 
 String pathSeparator) {
 // We need the -1 argument to split to make sure we get a trailing 
 // "" token if the base ends in the path separator and is therefore
 // a directory. We require directory paths to end in the path
 // separator -- otherwise they are indistinguishable from files.
 String[] base = basePath.split(Pattern.quote(pathSeparator), -1);
 String[] target = targetPath.split(Pattern.quote(pathSeparator), 0);
 // First get all the common elements. Store them as a string,
 // and also count how many of them there are. 
 String common = "";
 int commonIndex = 0;
 for (int i = 0; i < target.length && i < base.length; i++) {
 if (target[i].equals(base[i])) {
 common += target[i] + pathSeparator;
 commonIndex++;
 }
 else break;
 }
 if (commonIndex == 0)
 {
 // Whoops -- not even a single common path element. This most
 // likely indicates differing drive letters, like C: and D:. 
 // These paths cannot be relativized. Return the target path.
 return targetPath;
 // This should never happen when all absolute paths
 // begin with / as in *nix. 
 }
 String relative = "";
 if (base.length == commonIndex) {
 // Comment this out if you prefer that a relative path not start with ./
 //relative = "." + pathSeparator;
 }
 else {
 int numDirsUp = base.length - commonIndex - 1;
 // The number of directories we have to backtrack is the length of 
 // the base path MINUS the number of common path elements, minus
 // one because the last element in the path isn't a directory.
 for (int i = 1; i <= (numDirsUp); i++) {
 relative += ".." + pathSeparator;
 }
 }
 relative += targetPath.substring(common.length());
 return relative;
}

And here are tests to cover several cases:

public void testGetRelativePathsUnixy() 
{ 
 assertEquals("stuff/xyz.dat", FileUtils.getRelativePath(
 "/var/data/stuff/xyz.dat", "/var/data/", "/"));
 assertEquals("../../b/c", FileUtils.getRelativePath(
 "/a/b/c", "/a/x/y/", "/"));
 assertEquals("../../b/c", FileUtils.getRelativePath(
 "/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}
public void testGetRelativePathFileToFile() 
{
 String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
 String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";
 String relPath = FileUtils.getRelativePath(target, base, "\\");
 assertEquals("..\\..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}
public void testGetRelativePathDirectoryToFile() 
{
 String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
 String base = "C:\\Windows\\Speech\\Common";
 String relPath = FileUtils.getRelativePath(target, base, "\\");
 assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}
public void testGetRelativePathDifferentDriveLetters() 
{
 String target = "D:\\sources\\recovery\\RecEnv.exe";
 String base = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";
 // Should just return the target path because of the incompatible roots.
 String relPath = FileUtils.getRelativePath(target, base, "\\");
 assertEquals(target, relPath);
}
answered Aug 17, 2009 at 15:25

Comments

3

Actually my other answer didn't work if the target path wasn't a child of the base path.

This should work.

public class RelativePathFinder {
 public static String getRelativePath(String targetPath, String basePath, 
 String pathSeparator) {
 // find common path
 String[] target = targetPath.split(pathSeparator);
 String[] base = basePath.split(pathSeparator);
 String common = "";
 int commonIndex = 0;
 for (int i = 0; i < target.length && i < base.length; i++) {
 if (target[i].equals(base[i])) {
 common += target[i] + pathSeparator;
 commonIndex++;
 }
 }
 String relative = "";
 // is the target a child directory of the base directory?
 // i.e., target = /a/b/c/d, base = /a/b/
 if (commonIndex == base.length) {
 relative = "." + pathSeparator + targetPath.substring(common.length());
 }
 else {
 // determine how many directories we have to backtrack
 for (int i = 1; i <= commonIndex; i++) {
 relative += ".." + pathSeparator;
 }
 relative += targetPath.substring(common.length());
 }
 return relative;
 }
 public static String getRelativePath(String targetPath, String basePath) {
 return getRelativePath(targetPath, basePath, File.pathSeparator);
 }
}

public class RelativePathFinderTest extends TestCase {
 public void testGetRelativePath() {
 assertEquals("./stuff/xyz.dat", RelativePathFinder.getRelativePath(
 "/var/data/stuff/xyz.dat", "/var/data/", "/"));
 assertEquals("../../b/c", RelativePathFinder.getRelativePath("/a/b/c",
 "/a/x/y/", "/"));
 }
}
answered Oct 15, 2008 at 17:21

1 Comment

Instead of File.pathSeparator should be File.separator. pathSeparator should use only for split (regex), as for "////" regex (win path regex), result path will be incorrect.
3

Cool!! I need a bit of code like this but for comparing directory paths on Linux machines. I found that this wasn't working in situations where a parent directory was the target.

Here is a directory friendly version of the method:

 public static String getRelativePath(String targetPath, String basePath, 
 String pathSeparator) {
 boolean isDir = false;
 {
 File f = new File(targetPath);
 isDir = f.isDirectory();
 }
 // We need the -1 argument to split to make sure we get a trailing 
 // "" token if the base ends in the path separator and is therefore
 // a directory. We require directory paths to end in the path
 // separator -- otherwise they are indistinguishable from files.
 String[] base = basePath.split(Pattern.quote(pathSeparator), -1);
 String[] target = targetPath.split(Pattern.quote(pathSeparator), 0);
 // First get all the common elements. Store them as a string,
 // and also count how many of them there are. 
 String common = "";
 int commonIndex = 0;
 for (int i = 0; i < target.length && i < base.length; i++) {
 if (target[i].equals(base[i])) {
 common += target[i] + pathSeparator;
 commonIndex++;
 }
 else break;
 }
 if (commonIndex == 0)
 {
 // Whoops -- not even a single common path element. This most
 // likely indicates differing drive letters, like C: and D:. 
 // These paths cannot be relativized. Return the target path.
 return targetPath;
 // This should never happen when all absolute paths
 // begin with / as in *nix. 
 }
 String relative = "";
 if (base.length == commonIndex) {
 // Comment this out if you prefer that a relative path not start with ./
 relative = "." + pathSeparator;
 }
 else {
 int numDirsUp = base.length - commonIndex - (isDir?0:1); /* only subtract 1 if it is a file. */
 // The number of directories we have to backtrack is the length of 
 // the base path MINUS the number of common path elements, minus
 // one because the last element in the path isn't a directory.
 for (int i = 1; i <= (numDirsUp); i++) {
 relative += ".." + pathSeparator;
 }
 }
 //if we are comparing directories then we 
 if (targetPath.length() > common.length()) {
 //it's OK, it isn't a directory
 relative += targetPath.substring(common.length());
 }
 return relative;
}
Dan McClain
12k9 gold badges51 silver badges68 bronze badges
answered Dec 16, 2009 at 16:12

Comments

2

I'm assuming you have fromPath (an absolute path for a folder), and toPath (an absolute path for a folder/file), and your're looking for a path that with represent the file/folder in toPath as a relative path from fromPath (your current working directory is fromPath) then something like this should work:

public static String getRelativePath(String fromPath, String toPath) {
 // This weirdness is because a separator of '/' messes with String.split()
 String regexCharacter = File.separator;
 if (File.separatorChar == '\\') {
 regexCharacter = "\\\\";
 }
 String[] fromSplit = fromPath.split(regexCharacter);
 String[] toSplit = toPath.split(regexCharacter);
 // Find the common path
 int common = 0;
 while (fromSplit[common].equals(toSplit[common])) {
 common++;
 }
 StringBuffer result = new StringBuffer(".");
 // Work your way up the FROM path to common ground
 for (int i = common; i < fromSplit.length; i++) {
 result.append(File.separatorChar).append("..");
 }
 // Work your way down the TO path
 for (int i = common; i < toSplit.length; i++) {
 result.append(File.separatorChar).append(toSplit[i]);
 }
 return result.toString();
}
Dónal
188k177 gold badges589 silver badges848 bronze badges
answered Oct 15, 2008 at 17:13

Comments

1

Lots of answers already here, but I found they didn't handle all cases, such as the base and target being the same. This function takes a base directory and a target path and returns the relative path. If no relative path exists, the target path is returned. File.separator is unnecessary.

public static String getRelativePath (String baseDir, String targetPath) {
 String[] base = baseDir.replace('\\', '/').split("\\/");
 targetPath = targetPath.replace('\\', '/');
 String[] target = targetPath.split("\\/");
 // Count common elements and their length.
 int commonCount = 0, commonLength = 0, maxCount = Math.min(target.length, base.length);
 while (commonCount < maxCount) {
 String targetElement = target[commonCount];
 if (!targetElement.equals(base[commonCount])) break;
 commonCount++;
 commonLength += targetElement.length() + 1; // Directory name length plus slash.
 }
 if (commonCount == 0) return targetPath; // No common path element.
 int targetLength = targetPath.length();
 int dirsUp = base.length - commonCount;
 StringBuffer relative = new StringBuffer(dirsUp * 3 + targetLength - commonLength + 1);
 for (int i = 0; i < dirsUp; i++)
 relative.append("../");
 if (commonLength < targetLength) relative.append(targetPath.substring(commonLength));
 return relative.toString();
}
answered Oct 12, 2013 at 8:25

Comments

1

If you're writing a Maven plugin, you can use Plexus' PathTool:

import org.codehaus.plexus.util.PathTool;
String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);
answered Apr 9, 2017 at 14:53

Comments

0

Here a method that resolves a relative path from a base path regardless they are in the same or in a different root:

public static String GetRelativePath(String path, String base){
 final String SEP = "/";
 // if base is not a directory -> return empty
 if (!base.endsWith(SEP)){
 return "";
 }
 // check if path is a file -> remove last "/" at the end of the method
 boolean isfile = !path.endsWith(SEP);
 // get URIs and split them by using the separator
 String a = "";
 String b = "";
 try {
 a = new File(base).getCanonicalFile().toURI().getPath();
 b = new File(path).getCanonicalFile().toURI().getPath();
 } catch (IOException e) {
 e.printStackTrace();
 }
 String[] basePaths = a.split(SEP);
 String[] otherPaths = b.split(SEP);
 // check common part
 int n = 0;
 for(; n < basePaths.length && n < otherPaths.length; n ++)
 {
 if( basePaths[n].equals(otherPaths[n]) == false )
 break;
 }
 // compose the new path
 StringBuffer tmp = new StringBuffer("");
 for(int m = n; m < basePaths.length; m ++)
 tmp.append(".."+SEP);
 for(int m = n; m < otherPaths.length; m ++)
 {
 tmp.append(otherPaths[m]);
 tmp.append(SEP);
 }
 // get path string
 String result = tmp.toString();
 // remove last "/" if path is a file
 if (isfile && result.endsWith(SEP)){
 result = result.substring(0,result.length()-1);
 }
 return result;
}
answered May 30, 2014 at 8:20

Comments

0

Passes Dónal's tests, the only change - if no common root it returns target path (it could be already relative)

import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static org.apache.commons.io.FilenameUtils.normalizeNoEndSeparator;
import static org.apache.commons.io.FilenameUtils.separatorsToUnix;
import static org.apache.commons.lang3.StringUtils.getCommonPrefix;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.join;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ResourceUtils {
 public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {
 File baseFile = new File(basePath);
 if (baseFile.isFile() || !baseFile.exists() && !basePath.endsWith("/") && !basePath.endsWith("\\"))
 basePath = baseFile.getParent();
 String target = separatorsToUnix(normalizeNoEndSeparator(targetPath));
 String base = separatorsToUnix(normalizeNoEndSeparator(basePath));
 String commonPrefix = getCommonPrefix(target, base);
 if (isBlank(commonPrefix))
 return targetPath.replaceAll("/", pathSeparator);
 target = target.replaceFirst(commonPrefix, "");
 base = base.replaceFirst(commonPrefix, "");
 List<String> result = new ArrayList<>();
 if (isNotEmpty(base))
 result.addAll(nCopies(base.split("/").length, ".."));
 result.addAll(asList(target.replaceFirst("^/", "").split("/")));
 return join(result, pathSeparator);
 }
}
answered Dec 31, 2016 at 19:23

Comments

0

If Paths is not available for JRE 1.5 runtime or maven plugin

package org.afc.util;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
public class FileUtil {
 public static String getRelativePath(String basePath, String filePath) {
 return getRelativePath(new File(basePath), new File(filePath));
 }
 public static String getRelativePath(File base, File file) {
 List<String> bases = new LinkedList<String>();
 bases.add(0, base.getName());
 for (File parent = base.getParentFile(); parent != null; parent = parent.getParentFile()) {
 bases.add(0, parent.getName());
 }
 List<String> files = new LinkedList<String>();
 files.add(0, file.getName());
 for (File parent = file.getParentFile(); parent != null; parent = parent.getParentFile()) {
 files.add(0, parent.getName());
 }
 int overlapIndex = 0;
 while (overlapIndex < bases.size() && overlapIndex < files.size() && bases.get(overlapIndex).equals(files.get(overlapIndex))) {
 overlapIndex++;
 }
 StringBuilder relativePath = new StringBuilder();
 for (int i = overlapIndex; i < bases.size(); i++) {
 relativePath.append("..").append(File.separatorChar);
 }
 for (int i = overlapIndex; i < files.size(); i++) {
 relativePath.append(files.get(i)).append(File.separatorChar);
 }
 relativePath.deleteCharAt(relativePath.length() - 1);
 return relativePath.toString();
 }
}
answered Nov 26, 2017 at 9:18

Comments

0

I know this is a bit late but, I created a solution that works with any java version.

 public static String getRealtivePath(File root, File file) 
 {
 String path = file.getPath();
 String rootPath = root.getPath();
 boolean plus1 = path.contains(File.separator);
 return path.substring(path.indexOf(rootPath) + rootPath.length() + (plus1 ? 1 : 0));
 }
answered Dec 24, 2020 at 3:41

Comments

-1

org.apache.ant has a FileUtils class with a getRelativePath method. Haven't tried it myself yet, but could be worthwhile to check it out.

http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html#getRelativePath(java.io.File, java.io.File)

answered Jan 26, 2014 at 20:31

Comments

-1
private String relative(String left, String right){
 String[] lefts = left.split("/");
 String[] rights = right.split("/");
 int min = Math.min(lefts.length, rights.length);
 int commonIdx = -1;
 for(int i = 0; i < min; i++){
 if(commonIdx < 0 && !lefts[i].equals(rights[i])){
 commonIdx = i - 1;
 break;
 }
 }
 if(commonIdx < 0){
 return null;
 }
 StringBuilder sb = new StringBuilder(Math.max(left.length(), right.length()));
 sb.append(left).append("/");
 for(int i = commonIdx + 1; i < lefts.length;i++){
 sb.append("../");
 }
 for(int i = commonIdx + 1; i < rights.length;i++){
 sb.append(rights[i]).append("/");
 }
 return sb.deleteCharAt(sb.length() -1).toString();
}
answered Jul 31, 2015 at 9:18

Comments

-2

Psuedo-code:

  1. Split the strings by the path seperator ("/")
  2. Find the greatest common path by iterating thru the result of the split string (so you'd end up with "/var/data" or "/a" in your two examples)
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);
answered Oct 15, 2008 at 16:50

1 Comment

This answer is a hack at best. What about windows?

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.