Duplicate File Finder (Java - MD5 Hashtable)
Finds duplicate files by content using MD5 hashing. Uses a Hashtable<String, List<String>> where MD5 hash -> list of filenames. Groups with 2+ files are reported as duplicates.
The core is a command-line program that can be used standalone, wrapped by a Swing GUI with tree view and selective deletion.
Last updated: 2026-08-29 - NPE fix for tree renderer + README refresh
Files
DuplicateFinder.java- CLI core + hashtable logic. Can run standalone. (Hashtable<String, List<String>>atDuplicateFinder.java:114,filterDuplicatesatDuplicateFinder.java:145)DuplicateFinderGUI.java- Swing GUI wrapper (tree view + delete) that reusesDuplicateFinder's hashtable logic. (CheckBoxTreeCellRendereratDuplicateFinderGUI.java:347,CheckedNodeatDuplicateFinderGUI.java:56,startScanatDuplicateFinderGUI.java:474)build.bat/run.bat- Windows helpersbuild.sh/run.sh- bash helpers (Linux/macOS/WSL/Git Bash)README.md- this file
Requirements
- Java 8+ (tested on Temurin 1.8.0_504). Compatible with Java 8, 11, 17, 21. No external deps.
Build
javac DuplicateFinder.java DuplicateFinderGUI.java
Or:
build.bat :: Windows
./build.sh :: bash (Linux/macOS/WSL/Git Bash) - chmod +x build.sh first
Verify:
java DuplicateFinder --help
java DuplicateFinderGUI
:: bash:
./run.sh --help
./run.sh :: launches GUI
CLI Usage
java DuplicateFinder [options] <directory>
java DuplicateFinder --gui # launch GUI
java DuplicateFinder --help
java DuplicateFinderGUI # launch GUI directly
Options:
-r, --recursiverecurse into subdirectories (default)--no-recursiveonly top-level directory-h, --helpshow help--guilaunch Swing GUI
Examples:
java DuplicateFinder C:\Users\Me\Documents
java DuplicateFinder --no-recursive C:\Temp
java DuplicateFinder --gui
Output example:
Scanning: C:\Users\Me\Documents
Recursive: true
=== Duplicate Group 1 ===
MD5: 3579c8da7f1e0ad94656e76c886e5125 (3 files)
C:\...\a.txt (13 bytes)
C:\...\b.txt (13 bytes)
C:\...\sub\d.txt (13 bytes)
Summary: 1 duplicate group(s), 3 files involved.
Scanned in 43 ms.
Exit codes: 0 success, 1 error (bad args / missing path).
GUI Usage
java DuplicateFinderGUI
:: or
java DuplicateFinder --gui
java DuplicateFinderGUI "C:\path\to\start\dir"
GUI Features
- Directory text field + Browse button (JFileChooser - directories only)
- Recursive checkbox
- Find Duplicates / Cancel / Clear Results buttons
- Progress bar + status label (SwingWorker background thread, doesn't freeze UI)
- Tree view (
JTreewith checkboxes,DefaultTreeModel+CheckedNode):- Root = scan summary (invisible,
rootVisible=false), children = duplicate groups, leaves = files - Group node:
Group 1 - 3 files - 13 bytes each - MD5: abc...(bold, directory icon) - File node:
filename [parent folder] (size bytes)with checkbox (file icon, red when checked) - Click checkbox or press
Spaceon selected node to mark for deletion; group checkbox toggles all children (setNodeCheckedatDuplicateFinderGUI.java:423) - Custom
CheckBoxTreeCellRenderershowsJCheckBox + JLabelper row (DuplicateFinderGUI.java:347)
- Root = scan summary (invisible,
- Tree actions bar:
Auto-Select (keep 1 per group)- checks all but first file in each group (safe default,DuplicateFinderGUI.java:538)Clear Checks- unchecks all (DuplicateFinderGUI.java:561)Delete Checked Files- deletes with confirmation (shows count + bytes, warns if all in group checked,DuplicateFinderGUI.java:651)Open Location- opens containing folder viaDesktop.getDesktop().open()/explorerExpand All/Collapse All(DuplicateFinderGUI.java:571)- Label:
N file(s) checked, X bytes to free(updateCheckedInfoatDuplicateFinderGUI.java:636)
- Context popup (right-click): Auto-Select / Clear / Delete / Open Location / Copy Path
- Menu bar: File/Browse/Exit, Edit/Auto-Select/Delete, View/Expand/Collapse, Help/About
- Selection listener shows full path + size in status bar
Deletion Flow
- Scan -> tree populated, groups expanded (
displayResultsTreeatDuplicateFinderGUI.java:862,expandAll). - Use
Auto-Selector manually check unwanted duplicates (mouse click on row orSpace). - Review
Delete Checked Filesconfirmation dialog:- Lists files to delete (first 15 + count), total bytes, warnings if entire group selected.
- Requires YES confirmation (permanent
File.delete()withsetWritable(true)fallback).
- On success: tree nodes removed via
treeModel.removeNodeFromParent(), empty/single groups pruned, summary updated, status showsDeleted N file(s). - On failure (locked/read-only/not found): dialog lists failures, partial successes kept.
Safety:
Auto-Selectkeeps 1 copy per group. Manual delete warns if you check all files in a group.
The GUI calls DuplicateFinder.buildHashTableWithProgress() with a ProgressCallback to update the progress bar without freezing the EDT.
How it works
collectFiles(root, recursive)(DuplicateFinder.java:78) walks the file tree (recursive DFS).- For each file,
computeMD5(File)(DuplicateFinder.java:42) usesMessageDigest.getInstance("MD5")+ 8KB bufferedFileInputStreamto compute hex hash. - Hashtable population (
DuplicateFinder.java:114):Hashtable<String, List<String>> table = new Hashtable<>(); // md5 -> list List<String> list = table.get(md5); if (list == null) { list = new ArrayList<>(); table.put(md5, list); } list.add(file.getAbsolutePath()); filterDuplicates()(DuplicateFinder.java:145) keeps only entries wherelist.size() > 1.- CLI prints sorted groups; GUI builds
DefaultTreeModelwithCheckedNodeuser objects andCheckBoxTreeCellRenderer(DuplicateFinderGUI.java:347). Groups sorted by hash, files sorted by path.
Empty files (MD5 d41d8cd98f00b204e9800998ecf8427e) are considered duplicates if multiple empties exist. Unreadable files are skipped with warning.
Testing
Create a test_data example:
mkdir test_data\sub
echo Hello World > test_data\a.txt
copy test_data\a.txt test_data\b.txt
copy test_data\a.txt test_data\sub\d.txt
echo Different > test_data\c.txt
copy /b nul test_data\empty1.txt
copy /b nul test_data\empty2.txt
a.txt,b.txt,sub\d.txtall "Hello World" -> one group of 3empty1.txt,empty2.txt-> one group of 2 (if empties present)bin1.dat,sub\bin2.dat(bytes 0-255) -> one group of 2 if created
Run:
java DuplicateFinder test_data # expects 3 groups (or 1 if minimal set above)
java DuplicateFinder --no-recursive test_data # expects fewer groups (sub excluded)
# GUI: scan test_data, Auto-Select, Delete -> leaves 1 per group, re-scan shows 1 group then 0
Manual NPE regression test (fixed 2026-08-29):
javac -d . DuplicateFinder.java DuplicateFinderGUI.java
java DuplicateFinderGUI
# Click Find Duplicates immediately -> before fix threw NPE at DuplicateFinderGUI.java:391
# After fix: shows "Scanning: ..." placeholder without crash, then tree populates
Changelog
- 2026-08-29 (fix):
DuplicateFinderGUI.java:391NPE inCheckBoxTreeCellRenderer.getTreeCellRendererComponent()whendata.md5==nullfor placeholder root nodes ("Scanning...", "No duplicates...", "Cancelled"). Added null guard formd5(length check + empty). Also guardedDuplicateFinderGUI.java:670warningshortMd5substring. Verified viaRendererTest+ScanTestharness; CLI unaffected. - 2026-08-29: Added tree view with checkboxes, selective delete, auto-select keep-one, open location, expand/collapse, context menu.
- 2026-08-28: Initial CLI + basic GUI (
JTextArearesults).
Troubleshooting
NullPointerException at CheckBoxTreeCellRenderer.getTreeCellRendererComponent(DuplicateFinderGUI.java:391): fixed in latest version. UpdateDuplicateFinderGUI.javaor re-clone. Recompile withjavac.javac not found: install JDK 8+ (Temurin 8 used for testing atC:\Program Files\Eclipse Adoptium\jdk-8.0.504.1-hotspot\bin\javac.exe) and add toPATHor usebuild.batwhich auto-adds that path.HeadlessExceptionwhen running GUI on headless server: GUI requires display. Use CLIjava DuplicateFinder <dir>instead or run withxvfb/X forwarding.- Files not deleted: checked for
File.delete()returning false (locked/read-only). Close programs using the file, then retry. GUI retries withsetWritable(true).
Notes
- Uses
Hashtablespecifically (as required), notHashMap. - Large files are streamed, not loaded fully into memory.
- CLI is read-only (listing only); GUI deletion is opt-in with confirmation and prunes non-duplicate groups.