A symbolic link, also called a symlink or soft link, is a special file that stores the path of another file or directory, and most file operations on the link act on that target. To work with symbolic links in Java, we create one with Files.createSymbolicLink(link, target), check it with Files.isSymbolicLink() and read its target with Files.readSymbolicLink().
We use symbolic links to point a stable name such as current at the active release of an app, to share one large data folder between projects without copying it, and to keep configuration files in one place while tools look for them in another.
The following example creates a current link that points to a v2 release folder and inspects it.
Path releases = Files.createTempDirectory("releases");
Path v2 = Files.createDirectories(releases.resolve("v2"));
Path current = Files.createSymbolicLink(releases.resolve("current"), Path.of("v2"));
boolean isLink = Files.isSymbolicLink(current); // true
Path target = Files.readSymbolicLink(current); // v2
boolean isDir = Files.isDirectory(current); // true
boolean linkItself = Files.isDirectory(current, LinkOption.NOFOLLOW_LINKS); // false
The method isDirectory() answers for the target unless we pass NOFOLLOW_LINKS. We go through creating, checking and reading links, which operations follow them, broken links, hard links, Windows rules and a release switch that real deployments use.
1. Creating Symbolic Links
The method Files.createSymbolicLink(Path link, Path target, FileAttribute… attrs) takes the path of the new link first and the target second, the reverse of the ln -s target link command. The target does not have to exist, because the link stores only a path.

A relative target is resolved against the folder that contains the link, not against the working directory of the JVM. Relative links keep working when the whole folder moves, which is why the current link above stores v2 rather than an absolute path.
Path data = Files.createTempDirectory("data");
Path settings = Files.writeString(data.resolve("settings.json"), "{}");
Path absoluteLink = Files.createSymbolicLink(data.resolve("abs-link.json"), settings);
Path relativeLink = Files.createSymbolicLink(data.resolve("rel-link.json"), Path.of("settings.json"));
String viaRelative = Files.readString(relativeLink); // "{}"
Path again = Files.createSymbolicLink(relativeLink, Path.of("settings.json")); // FileAlreadyExistsException
The method throws FileAlreadyExistsException when a file with the link’s name exists, UnsupportedOperationException when the file system has no symbolic links, and FileSystemException when the user lacks the right to create them, which matters on Windows as section 7 explains. To replace a link, we delete the old one first or use the atomic switch from section 8.
2. Checking Symbolic Links
The method Files.isSymbolicLink(Path) returns true only for a link. It returns false for a regular file or folder, for a path that does not exist, and when the check itself fails, so false does not prove the path exists.
Path docs = Files.createTempDirectory("docs");
Path guide = Files.writeString(docs.resolve("guide.txt"), "read me");
Path shortcut = Files.createSymbolicLink(docs.resolve("shortcut.txt"), Path.of("guide.txt"));
boolean onLink = Files.isSymbolicLink(shortcut); // true
boolean onFile = Files.isSymbolicLink(guide); // false
boolean onMissing = Files.isSymbolicLink(docs.resolve("nothing.txt")); // false
boolean regularTarget = Files.isRegularFile(shortcut); // true
The last line shows the usual trap. Methods such as isRegularFile() and isDirectory() follow the link and describe the target, so a file scanner that wants to skip links must ask isSymbolicLink() first or pass LinkOption.NOFOLLOW_LINKS.
3. Finding the Target of a Link
The method Files.readSymbolicLink(Path) returns the target path in the form it was stored, which can be relative. It throws NotLinkException when the path is not a link. To get the absolute target, we resolve a relative result against the link’s folder, or call toRealPath(), which follows every link in the path and needs the target to exist.
Path shop = Files.createTempDirectory("shop");
Path catalog = Files.createDirectories(shop.resolve("catalog-2026"));
Path latest = Files.createSymbolicLink(shop.resolve("latest"), Path.of("catalog-2026"));
Path stored = Files.readSymbolicLink(latest); // catalog-2026
Path resolved = latest.resolveSibling(stored);
boolean sameDir = Files.isSameFile(resolved, catalog); // true
String realName = latest.toRealPath().getFileName().toString(); // "catalog-2026"
Path notALink = Files.readSymbolicLink(catalog); // NotLinkException
The method Files.isSameFile() compares the files behind two paths after following links, so it tells us whether a link and a path end at the same file even when their path strings differ.
4. Operations That Follow Links and Operations That Do Not
Reading, writing and most checks follow a symbolic link to its target, while deleting, moving and walking a folder tree act on the link itself. Knowing which is which keeps us from deleting data by accident or copying a link where we wanted the file.
| Operation | Default behavior | Option to change it |
|---|---|---|
| Files.readString(), newInputStream(), write() | Follows the link | None |
| Files.exists(), isDirectory(), isRegularFile(), readAttributes() | Follows the link | LinkOption.NOFOLLOW_LINKS |
| Files.copy() | Copies the target file | LinkOption.NOFOLLOW_LINKS copies the link |
| Files.delete() | Deletes the link only | None |
| Files.move() | Moves the link only | None |
| Files.walk(), walkFileTree() | Does not enter linked folders | FileVisitOption.FOLLOW_LINKS |
Path media = Files.createTempDirectory("media");
Path song = Files.writeString(media.resolve("song.mp3"), "mp3");
Path favorite = Files.createSymbolicLink(media.resolve("favorite.mp3"), Path.of("song.mp3"));
Path copiedFile = Files.copy(favorite, media.resolve("copy1.mp3"));
Path copiedLink = Files.copy(favorite, media.resolve("copy2.mp3"), LinkOption.NOFOLLOW_LINKS);
boolean firstIsLink = Files.isSymbolicLink(copiedFile); // false
boolean secondIsLink = Files.isSymbolicLink(copiedLink); // true
Files.delete(favorite);
boolean songKept = Files.exists(song); // true
Following links during a walk can loop forever when a link points to one of its own parent folders. With FOLLOW_LINKS, the JDK detects that cycle and throws FileSystemLoopException, which Files.walk() wraps in an UncheckedIOException.
Path project = Files.createTempDirectory("project");
Path nestedDir = Files.createDirectories(project.resolve("src"));
Path backLink = Files.createSymbolicLink(nestedDir.resolve("up"), project);
long entries;
try (Stream<Path> paths = Files.walk(project)) {
entries = paths.count();
}
long counted = entries; // 3
try (Stream<Path> paths = Files.walk(project, FileVisitOption.FOLLOW_LINKS)) {
long looped = paths.count(); // UncheckedIOException: FileSystemLoopException
}
5. Broken Links
A link whose target was deleted or never existed is called a broken or dangling link. Files.exists() follows the link and returns false, so a cleanup job that checks only exists() never sees the link. Passing NOFOLLOW_LINKS finds the link itself.
Path cache = Files.createTempDirectory("cache");
Path tempFile = Files.writeString(cache.resolve("page.html"), "html");
Path cached = Files.createSymbolicLink(cache.resolve("index.html"), Path.of("page.html"));
Files.delete(tempFile);
boolean targetThere = Files.exists(cached); // false
boolean linkThere = Files.exists(cached, LinkOption.NOFOLLOW_LINKS); // true
boolean broken = Files.isSymbolicLink(cached) && Files.notExists(cached); // true
String content = Files.readString(cached); // NoSuchFileException
A check such as broken above is what a deployment script runs before a restart, so a missing release folder shows up as a clear error instead of a server that cannot find its files.
6. Hard Links
A hard link is a second directory entry for the same file data, so both names are equal and neither is the original. The method Files.createLink(Path link, Path existing) creates one. The file data stays on disk until the last name is deleted, so a hard link keeps working after the first name is gone.
Path photos = Files.createTempDirectory("photos");
Path original = Files.writeString(photos.resolve("beach.jpg"), "jpg");
Path second = Files.createLink(photos.resolve("beach-copy.jpg"), original);
boolean sameData = Files.isSameFile(original, second); // true
Files.writeString(original, "edited");
String throughSecond = Files.readString(second); // "edited"
Files.delete(original);
String survived = Files.readString(second); // "edited"
Hard links have stricter rules than symbolic links, which the table sums up.
| Feature | Symbolic link | Hard link |
|---|---|---|
| Java method | Files.createSymbolicLink() | Files.createLink() |
| Stores | A path to the target | A second name for the same data |
| Target deleted | Link breaks | Data stays reachable |
| Directories | Allowed | Not allowed on most systems |
| Across disks | Allowed | Same file system only |
| Detect with | Files.isSymbolicLink() | Files.isSameFile() or the unix:nlink attribute |
7. Symbolic Links on Windows
Windows supports symbolic links on NTFS, but creating one needs the SeCreateSymbolicLinkPrivilege right. Administrators have it, and since Windows 10 any user can create links once Developer Mode is turned on, which the JDK supports since Java 13. Without either, createSymbolicLink() throws a FileSystemException with the message “A required privilege is not held by the client”.
Tests that create links therefore fail on many Windows build agents. A JUnit test can catch the exception and skip itself with an assumption, so the test suite runs everywhere and covers links where the platform allows them. Shortcuts (.lnk files) and directory junctions are different Windows features, and isSymbolicLink() returns false for a .lnk file.
8. Switching a Release with an Atomic Link Update
A common deployment layout keeps every release in its own folder and points the web server at a current link. Switching to a new release means changing the link, and a delete followed by a create leaves a short window where current does not exist and requests fail.
The safe switch creates the new link under a temporary name and renames it over the old one with ATOMIC_MOVE. A rename replaces the old link in one step on Linux and macOS, so every request sees either the old release or the new one. The rename or move a file article explains ATOMIC_MOVE in detail.
static void switchRelease(Path currentLink, Path newTarget) throws IOException {
Path temp = currentLink.resolveSibling(currentLink.getFileName() + ".next");
Files.deleteIfExists(temp);
Files.createSymbolicLink(temp, newTarget);
Files.move(temp, currentLink, StandardCopyOption.ATOMIC_MOVE);
}
Path app = Files.createTempDirectory("app");
Files.createDirectories(app.resolve("release-1"));
Files.createDirectories(app.resolve("release-2"));
Path live = Files.createSymbolicLink(app.resolve("current"), Path.of("release-1"));
switchRelease(live, Path.of("release-2"));
Path nowServing = Files.readSymbolicLink(live); // release-2
A rollback is the same call with the previous release folder. Old release folders can be removed later with a recursive delete, which must not follow the current link into the live release.
9. Symbolic Links in Java FAQs
Deleting through a link, spotting broken links and walking linked folders are what people search for most.
9.1. Does Files.delete() delete the target of a symbolic link?
No. It deletes the link and leaves the target untouched, as the last lines of the example in section 4 show.
9.2. How do we check whether a symbolic link is broken?
Combine Files.isSymbolicLink(path) with Files.notExists(path). The first call looks at the link, and the second follows it and finds no target.
9.3. Does Files.walk() follow symbolic links?
No, not by default. A linked folder appears as one entry and the walk does not enter it. Passing FileVisitOption.FOLLOW_LINKS enters linked folders and throws FileSystemLoopException on cycles.
9.4. How do we get the absolute path a symbolic link points to?
Call link.toRealPath(), which follows all links and returns an absolute path, but needs the target to exist. For a possibly broken link, use link.resolveSibling(Files.readSymbolicLink(link)), which works without touching the target.
9.5. Can java.io.File work with symbolic links?
Only partly. The File class has no method to create, detect or read links, and most of its methods, such as exists() and isDirectory(), follow links. Convert with toPath() and use Files, as covered in the Path guide.
10. Conclusion
Java handles symbolic links through Files, with createSymbolicLink() to create them, isSymbolicLink() to detect them and readSymbolicLink() or toRealPath() to find the target. Reads and most checks follow a link, while delete, move and tree walks act on the link itself, and NOFOLLOW_LINKS switches the checks to the link.
Hard links from createLink() are second names for the same data and survive the deletion of the first name. On Windows, link creation needs a privilege or Developer Mode, and a temporary link plus ATOMIC_MOVE switches a current release link without a gap. Our Java IO tutorial index points to the guides on file attributes, copying and directory walks.
11. References
- Files Javadoc (Java 25)
- LinkOption Javadoc
- FileVisitOption Javadoc
- Dev.java, Links, Symbolic or Otherwise
- Microsoft, Create symbolic links policy
Happy Learning !!