Multi-threaded File Downloader CLI
Build a Java command-line tool that downloads large files faster by splitting them into parts and downloading the parts at the same time. You run it like this: java -jar downloader.jar <url> -o file.zip -t 4. It asks the server for the file size, splits it into byte ranges, downloads the ranges in parallel on a thread pool, and writes them into the final file while showing a progress bar. If the download is interrupted, running the same command again resumes it instead of starting over. You'll learn java.net.http.HttpClient, HTTP range requests, ExecutorService and futures, thread-safe progress counting, and writing to specific positions in a file.
- Estimate
- ~13.5h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Java HttpClient · ExecutorService
Project roadmap
- 01
Set up the project and arguments
~1.5hCreate a Maven or Gradle project for Java 17 or newer that builds a runnable jar. Parse the arguments: the URL (required), -o for the output file (default: the last part of the URL path) and -t for the number of threads (default 4, between 1 and 16). Print the usage and exit with code 2 for missing or invalid arguments. Done when: the jar runs and prints the usage for bad input.
- 02
Check the file size and range support
~1.5hSend a HEAD request with HttpClient, following redirects, and read the Content-Length and Accept-Ranges headers. If the server doesn't report a size or doesn't accept byte ranges, fall back to a normal single-threaded download. Print the file size in MB and whether a parallel download is possible. Done when: the tool reports the size and range support for a real URL.
- 03
Split the file into chunks
~1.5hWrite a pure function that takes the total size and the thread count and returns a list of byte ranges (start and end, both inclusive) that cover the whole file exactly once, with the last chunk taking any remainder. Unit test it with a size of 1, sizes that don't divide evenly, and more threads than bytes. Done when: the function and its tests exist, and every byte is covered exactly once.
- 04
Download the chunks in parallel
~3hCreate a fixed thread pool with ExecutorService. For each range, submit a task that sends a GET request with the header Range: bytes=start-end, checks for a 206 Partial Content response, and writes the bytes at the right position in the output file using a FileChannel or RandomAccessFile. Wait for all futures; retry a failed chunk up to 3 times before failing the whole download. Done when: a file of several megabytes downloads correctly with 4 threads.
- 05
Show progress
~1.5hCount downloaded bytes across all threads with an AtomicLong, and print a progress line that updates in place every 500 ms: the percentage, MB downloaded out of the total, and the speed in MB/s. Stop updating when all tasks are done. Done when: the progress line updates smoothly during a download and ends at 100%.
- 06
Resume interrupted downloads
~2.5hKeep a state file next to the output (the output name plus .part.json) listing each chunk and how many of its bytes are finished, and update it every second. On start, if the state file exists and the file size hasn't changed, download only the missing bytes of each chunk. Delete the state file after a successful download. Done when: stopping the tool with Ctrl+C and running it again finishes the file without downloading completed parts again.
- 07
Verify and test
~2hAdd an optional --sha256 argument: after downloading, compute the file's SHA-256 with MessageDigest and fail if it doesn't match. Write a test that downloads a file from a local test server (for example com.sun.net.httpserver.HttpServer serving a file with Range support) and compares it with the original. Done when: the tests pass and --sha256 detects a corrupted file.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack