Java Files: Paths, Create, and Delete
Understand the difference between describing a location and changing the filesystem. Build paths, create disposable files without overwriting, and verify that targeted deletion leaves unrelated entries untouched.
Before you start: review resource ownership and cleanup, checked exceptions, and arrays. Each example is a complete Java 21 Main.java program with no input required.
What you will be able to do
- Distinguish a Path value from a filesystem entry.
- Explain relative, absolute, resolved, and normalized paths.
- Create files and directories without accidentally overwriting existing data.
- Interpret existence and type checks without assuming they guarantee later access.
- Delete only exact owned targets and understand nonempty-directory failures.
- Build and verify a temporary workspace that preserves unrelated entries.
A Path value is not the file itself
Path.of builds a value describing a location in a filesystem. It does not create directories, reserve a filename, or prove that an entry exists. The first example only examines a relative path's components; it performs no file creation or deletion.
Files supplies operations such as createFile, isRegularFile, size, and delete. This separation helps you distinguish a valid path expression from successful filesystem access. A syntactically valid path can still refer to something missing, inaccessible, or different from what you expected.
Use Path.of with separate components instead of manually joining names with a hard-coded separator. Path syntax and case behavior depend on the filesystem provider and operating system. These examples avoid printing generated temporary locations so the expected learning output stays reproducible.
Main.java · A Path describes a location without creating it
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Name: lesson.txt
Parent name: reports
Absolute: false
Parts: 2
Relative paths need a working-directory context
reports/lesson.txt is relative: the filesystem operation interprets it from the process's default directory, not necessarily from the folder containing Main.java. Launching the same program from another directory can therefore change which relative location is used.
toAbsolutePath converts a relative value using that default-directory context. It does not create the target or guarantee that it exists. Do not assume an absolute path means a real, canonical, readable file; it only removes one kind of location ambiguity.
On the website, Java runs in an isolated container without a mount of your project files. Temporary examples use the container's temporary filesystem, not your computer's Downloads folder or the website host's repository. Each run is independent and its files are not persisted between runs.
Resolve components without mistaking normalization for security
resolve combines a base path with another path. When the other path is absolute, it normally replaces the base rather than remaining below it. The worked example uses only relative components and normalize removes the lexical drafts/.. pair.
normalize works on path components without checking the filesystem. It does not inspect symbolic links or prove where a later file operation will actually land. toRealPath, by contrast, accesses the filesystem and normally resolves an existing target, so it can fail if the target is absent or inaccessible.
Do not treat normalize().startsWith(base) as a complete security boundary for untrusted filenames. Symbolic links, concurrent changes, provider rules, and ownership all matter. Our runnable mutations use fixed names inside freshly created private temporary workspaces, not arbitrary user-supplied paths.
Main.java · Resolve names and normalize path components
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Original parts: 4
Normalized parts: 2
Same normalized value: true
Absolute result: true
Trace the lexical path change
- Start with the reports component.
- Add drafts, then .., then lesson.txt using resolve.
- normalize removes the drafts/.. pair in this lexical example.
- The resulting value matches reports plus lesson.txt, without creating either entry.
This is not a symbolic-link resolution or authorization check. An absolute value can still refer to a missing or inaccessible target.
Create a workspace you own before experimenting
Files.createTempDirectory creates a new directory with a generated name, and createTempFile similarly creates a new empty file. Temporary does not mean Java automatically deletes every item at the end of a method. The program still needs a deliberate cleanup policy.
LessonWorkspace owns one new directory and records only the scratch and keep files it successfully creates. Its AutoCloseable cleanup attempts exact deletions of those owned paths and then the directory. It does not walk arbitrary trees or delete unknown entries to force the parent to disappear.
The helper continues cleanup attempts after an IOException and retains later cleanup failures as suppressed evidence. Try-with-resources then preserves a body exception if closing also fails. This is a small single-process teaching workspace, not a hardened multi-user filesystem service.
Main.java · Create an empty file in a fresh temporary directory
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Name: scratch.txt
Regular file: true
Bytes: 0
Read the ownership helper in four steps
- Create a brand-new generated temporary directory.
- Record scratch or keep only after the corresponding createFile succeeds.
- At close, attempt each recorded file before attempting the directory.
- Retain cleanup errors and refuse to recursively remove unknown contents.
Files that were manually removed earlier are simply absent at cleanup. Unknown files are not added to ownership by scanning the directory.
Create a new entry instead of silently replacing an old one
Files.createFile creates an empty file and fails if that entry already exists. It does not truncate a previously existing report. The duplicate example handles FileAlreadyExistsException and verifies the original file remains a regular file.
Calling Files.exists first and then createFile does not make the sequence atomic: another actor can change the entry between the check and operation. If the goal is create only if absent, perform createFile and handle its documented outcome rather than relying on the earlier observation.
The helper assigns its ownership field only after createFile returns successfully. A failed duplicate request therefore does not replace the tracked owned path or grant ownership of some unrelated entry. Text content and append-versus-overwrite policies belong to the next lesson.
Main.java · Existing files are not silently overwritten
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Rejected duplicate creation.
Original still a file: true
Choose between one directory and missing parent directories
Files.createDirectory creates one directory but requires its parent to exist. The first nested attempt fails because reports has not been created. Files.createDirectories is the appropriate operation when the intended policy is to create missing parent directories as well.
Calling createDirectories again on the same existing directory is normally fine, but a file in that path or an access failure can still cause an exception. It is not a command to replace conflicting files. A partially completed multi-directory operation also does not promise automatic rollback on failure.
This example cleans up only its exact newly owned drafts, reports, and temporary root paths, child before parent. The small finally demonstrates that order; if one of those deletes fails, later statements can be skipped and a prior error can be masked. The reusable lab helper improves that by attempting each cleanup and retaining failures.
Main.java · Create missing parents deliberately
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Missing parent rejected.
Parent created: true
Nested created: true
Checks are observations, not guarantees about the next operation
For known accessible entries in our workspace, isDirectory and isRegularFile distinguish the parent from the empty scratch file. exists and notExists also describe the absent fixed target. Files.size returns bytes for our regular empty file, which is why its size is zero.
exists returning false does not always prove absence: Java may be unable to determine existence. notExists is not simply the boolean negation of exists; both can be false when the answer is unknown. Type checks may likewise return false when the type cannot be determined.
Many checks follow symbolic links by default, while suitable LinkOption.NOFOLLOW_LINKS checks can inspect the directory entry differently. Checks also become stale immediately if another actor changes the filesystem. Handle the actual operation's IOException rather than using a prior boolean as permission or certainty.
Main.java · Inspect a known file, directory, and absent target
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Root directory: true
Scratch file: true
Absent exists: false
Absent notExists: true
Delete exactly the entry whose ownership you can explain
deleteIfExists returns true when an entry was deleted and false if it was already absent. It can still throw for other problems; its name does not mean ignore every failure. Files.delete instead reports a missing target as a failure, which the example demonstrates explicitly.
Removing scratch does not remove keep, even though they share a parent. The later workspace cleanup removes keep only because this same program created and owns it. Never substitute a home directory, repository root, or unknown path for a demonstration target.
These APIs do not move entries to the operating system's trash and do not promise an undo function. We use empty disposable files to avoid data loss. Deleting a symbolic link generally removes the link itself rather than its target; do not confuse that rule with assumptions about links elsewhere in a path.
Main.java · Delete one exact file without touching its neighbor
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First deletion: true
Second deletion: false
Neighbor remains: true
Strict deletion reports missing file.
A nonempty directory is a boundary, not an invitation to recurse
Files.delete does not recursively erase a directory tree. In the example, the directory still contains keep.txt, so deleting the parent fails and the child remains. The helper later removes the owned child before attempting the parent again.
If an unexpected file appears in the workspace, our cleanup should fail to remove the nonempty parent rather than deleting the unknown file. Leaving a disposable parent with an explicit error is safer than expanding the deletion scope to entries the program cannot account for.
Some exception subclasses and filesystem details depend on the provider. The website's isolated filesystem supplies the demonstrated exceptions; local machines may report another IOException for related failures. Read the actual target and diagnostic instead of suppressing every cleanup problem.
Main.java · A directory is not deleted recursively
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Nonempty directory preserved.
Child remains: true
Verify the lifecycle and the untouched neighbor
The lab creates a new workspace, creates scratch and keep, rejects duplicate scratch creation, deletes only scratch, and verifies keep remains. Leaving the resource scope then cleans up the known owned files and empty root. This tests both the intended action and what must not change.
Also test an unexpected extra entry using a separate disposable test workspace. Closing must preserve that entry and report the nonempty parent, not silently delete it. The test can then remove only the extra item it created and retry exact cleanup.
Keep path manipulation separate from filesystem mutation, and keep a record of which operations successfully created owned entries. The next lesson adds text reading and writing with explicit charsets; successful path creation alone does not imply any content has been stored.
Repair exercise: the parent is not the intended child
The program intends to remove scratch.txt but passes its parent directory to delete. Run it and explain why the child remains. Repair the exact target, preserving the workspace scope and its cleanup; do not add recursive deletion.
Main.java · Inspect the incorrect deletion target
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The starter catches the nonempty-parent failure and currently prints:
Wrong target: parent is not empty.
Scratch remains: true
Reveal the explained repair
The path passed to delete determines the entry removed. Deleting a parent does not mean delete a child inside it, and a nonempty parent is not recursively removed. Passing the already-owned scratch path fixes the intended action without expanding its scope.
Main.java · Delete only the owned scratch file
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Scratch deleted.
Scratch remains: false
Independent lab: create two files, remove one, preserve the neighbor
Build a temporary file lifecycle using a workspace owned by this program. Create two empty files, delete one, prove the neighbor survives, and close the scope to clean up only known owned paths. No user files are read or removed.
- Implement LessonWorkspace implements AutoCloseable. Initialize a private final directory with Files.createTempDirectory and expose directory(). The constructor declares IOException. Do not use a broad or user-supplied root.
- Implement createScratch() and createKeep() using fixed filenames scratch.txt and keep.txt resolved under the directory. Track each returned Path only after Files.createFile succeeds; duplicate creation must propagate rather than overwrite.
- Implement close() by attempting deleteIfExists for tracked scratch, tracked keep, then the directory. Skip null uncreated paths. Continue after each IOException, preserve the first, attach later failures as suppressed, and throw the accumulated failure after all attempts. Never recurse into unknown contents.
- In main create the workspace in try-with-resources, store its root for the post-close check, create both empty files, inspect scratch's regular-file type and zero size, and catch duplicate creation specifically.
- Delete scratch with deleteIfExists twice, print true then false, and prove keep is still a regular file before leaving the scope. After successful close, verify the owned directory no longer exists.
- Test partial creation, repeated cleanup, duplicate creation, and an unexpected test-created neighbor. Unknown entries must be preserved even if that means closing reports a nonempty directory. Remove only your test's own extra item before retrying cleanup.
Before running, identify which statement creates each owned entry and which statement may remove it. The completed program should produce:
Scratch is a file: true
Scratch bytes: 0
Duplicate rejected.
First deletion: true
Second deletion: false
Keep preserved: true
Owned directory removed: true
Main.java · Build the temporary-file lifecycle
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stage 1 hints: own a fresh root and fixed targets
Create the temporary directory once. Resolve scratch.txt and keep.txt under it, and record each successful creation. Do not catch a duplicate and pretend a new file was created or truncate it to make the second attempt succeed.
Stage 2 hints: delete by exact ownership
Delete scratch only and confirm keep survives. In close, use the recorded paths followed by the root. An array of these three exact targets is sufficient; no recursive directory walk belongs in this lab.
Stage 3 hints: retain cleanup evidence
Try every exact cleanup even after an IOException, retaining the first and suppressing later failures. Throw the retained failure afterward. Try-with-resources then preserves a body exception if cleanup also fails. An unknown child should remain, not be removed to force a successful root deletion.
Reveal the complete explained solution
Main.java · Complete owned temporary-file lab
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Creation and deletion operate only on fixed names under a fresh temporary root. Duplicate creation cannot truncate the existing file. The first scratch deletion leaves keep untouched; close later removes keep because it too is owned, then removes the empty directory. Unknown contents are never recursively erased to manufacture successful cleanup.
Test changes and boundaries
- Verify fresh scratch and keep are distinct empty regular files, duplicate creation throws, and deleting scratch leaves keep unchanged until owned cleanup.
- Close a workspace with no files, only scratch, only keep, or both files. Repeating successful cleanup should not fail because deleteIfExists tolerates already absent owned paths.
- Add an extra disposable test-owned file not tracked by LessonWorkspace. close must leave it intact and report the nonempty root. Remove only that test file and retry cleanup.
- Compare relative path components, resolve, normalize, and absolute conversion without relying on random temporary directory names. Explain why none of those lexical operations proves safe access to untrusted filesystem entries.
- Trigger a body failure while an unexpected entry prevents cleanup. Verify the body failure stays primary and cleanup is suppressed; then remove only the test-created entry and retry the exact owned cleanup.
Common mistakes and how to diagnose them
Assuming Path.of creates the file
Path is a value describing a location. Use an appropriate Files operation to create or inspect an actual entry, and handle its result or exception.
Assuming relative means next to Main.java
A relative path is interpreted from the process's default directory. Make context explicit instead of assuming your editor and deployed process launch from the same place.
Using exists as a guarantee
Checks can be inconclusive or become stale. Perform the intended operation and handle its documented failure instead of treating an earlier boolean as proof.
Deleting the parent to remove one child
Pass the exact owned child path. Deleting a nonempty directory fails rather than recursively deleting its contents, and broadening deletion scope can destroy unrelated data.
Treating deleteIfExists as ignore-all-errors
Only absence is tolerated. Permission, nonempty-directory, and other I/O failures can still occur and should remain visible.
Treating normalization as a complete security check
Lexical cleanup does not resolve symbolic links or prevent concurrent changes. These examples use newly owned temporary roots and fixed names, not arbitrary user targets.
Frequently asked questions
Does creating a Path touch the filesystem?
Constructing the path value does not create an entry. Operations such as createFile, size, delete, and toRealPath access filesystem state and can fail separately.
What is the difference between createDirectory and createDirectories?
createDirectory creates one directory with an existing parent. createDirectories creates missing parents as needed and tolerates an existing directory, but does not replace conflicting files or promise rollback after partial failure.
Are exists and notExists exact opposites?
No. When existence cannot be determined, both can be false. Either check is only an observation and does not guarantee that the next operation will succeed.
Does Files.delete move files to the trash?
No. These examples use disposable empty items because the deletion API does not supply an ordinary trash or undo workflow. Never substitute valuable or broadly scoped paths.
Will the website examples delete my project files?
The supplied programs use fresh temporary directories in the isolated Java container, without a host project mount. They remove only their owned disposable items. If you copy code locally, retain that temporary scope.
Why preserve an unknown file even if cleanup then fails?
The helper cannot claim ownership merely because a file appeared under its parent. Reporting a nonempty directory is preferable to recursively removing entries it did not create or track.
Describe a path, operate deliberately, and clean up only what you own
A Path value does not create a file. Creation, inspection, and deletion are separate operations with their own outcomes. Relative context matters, checks can be stale or inconclusive, and normalization is not a complete security boundary. Fresh temporary targets let you learn real behavior without touching valuable data.
Explain why deleting scratch preserves keep, why duplicate creation must not truncate a report, and why unknown contents stop root cleanup. Next: reading and writing text files, where explicit encodings and append-versus-overwrite policy become part of the contract.
Can you distinguish a path, a file, and an owned deletion target?
Answer six questions and read the explanations. Quiz answers are temporary and do not save course progress.
References and related learning
These examples, explanations, and exercises are original SovranCode material. The official Java 21 API references document provider-dependent path and filesystem behavior.
Review resource cleanup · Review checked failures · Java course roadmap.
Last updated: