HTTP File Downloader (java, written by Claude Code)
envgap__claude-code__java-t3-24
Written by a coding agent; not on GitHubWritten 2026-02-28
01 / FAILURE SIGNATURE
As the study recorded it
ClassNotFoundException: com.downloader.FileDownloader
Not scored yet.
- The failure reproduces in a clean container and the known fix makes the project run; it has not been scored yet.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/java-t3 #24 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: HTTP File Downloader Write a program that downloads files from HTTP/HTTPS URLs with support for resumable downloads, progress tracking, concurrent downloads, and checksum verification. FUNCTIONAL REQUIREMENTS: - Accept one or more URLs as command-line arguments - Display a progress bar during download showing: percentage complete, bytes downloaded, total size, download speed, and estimated time remaining - Support resumable downloads via HTTP Range headers: if a download is interrupted, restarting with the same URL and output path should resume from where it stopped via --resume flag - Support concurrent downloading of multiple files via --parallel flag with configurable thread count (--threads, default 4) - Support downloading all URLs listed in a text file (one URL per line) via --list flag - Verify downloaded file integrity via --checksum flag accepting algorithm:hash format (e.g., --checksum sha256:abc123...) - Support custom HTTP headers via --header flag (e.g., --header "Authorization: Bearer token") - Support following HTTP redirects (up to 10 hops) and report the final URL - Set connection timeout via --timeout flag (default 30 seconds) and retry failed downloads via --retries flag (default 3) with exponential backoff - Save files to a directory specified by --output flag (default: current directory), using the filename from the URL or Content-Disposition header - Print a download summary to console: file name, size, time taken, average speed, and checksum verification result - If no URLs are given, download a set of sample public domain text files from Project Gutenberg, display progress for each, and print a summary table - Handle errors: DNS resolution failures, SSL certificate errors, HTTP 4xx/5xx responses, disk full, and network timeouts Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include: - Source code - pom.xml with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
FileDownloader.java
package com.downloader;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
/**
* HTTP File Downloader with progress bars, resumable downloads,
* concurrent transfers, and checksum verification.
*
* Dependencies: java.net.http (built-in), picocli (4.7.5), Jackson (2.16.1)
*/
@Command(
name = "downloader",
description = "HTTP File Downloader with progress, resume, and checksum support.",
mixinStandardHelpOptions = true,
version = "1.0.0"
)
public class FileDownloader implements Callable<Integer>, Closeable {
private static final int DEFAULT_CHUNK_SIZE = 65536;
private static final int DEFAULT_TIMEOUT_SECONDS = 30;
private static final int DEFAULT_MAX_RETRIES = 3;
private static final int DEFAULT_MAX_WORKERS = 4;
private static final int PROGRESS_BAR_WIDTH = 50;
@Parameters(description = "URLs to download")
private List<String> urls = new ArrayList<>();
@Option(names = {"-o", "--output-dir"}, description = "Output directory (default: current)")
private String outputDirStr = ".";
@Option(names = {"-w", "--workers"}, description = "Number of concurrent workers (default: 4)")
private int workerCount = DEFAULT_MAX_WORKERS;
@Option(names = "--checksum", description = "Expected checksum (single URL)")
private String checksum;
@Option(names = "--algorithm", description = "Checksum algorithm (default: SHA-256)")
private String algorithm = "SHA-256";
@Option(names = "--no-resume", description = "Disable resumable downloads")
private boolean noResume;
@Option(names = "--no-progress", description = "Disable progress bars")
private boolean noProgress;
@Option(names = "--json", description = "Download from JSON config file")
private String jsonFile;
@Option(names = "--timeout", description = "Request timeout in seconds (default: 30)")
private int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS;
@Option(names = "--retries", description = "Maximum retries (default: 3)")
private int maxRetries = DEFAULT_MAX_RETRIES;
private HttpClient client;
private ObjectMapper mapper;
private Path outputDir;
private int chunkSize = DEFAULT_CHUNK_SIZE;
private ExecutorService executor;
/**
* Represents a download task with status tracking.
*/
public static class DownloadTask {
public String url;
public String outputPath;
public String expectedChecksum;
public String checksumAlgorithm;
public long downloadedBytes;
public long totalBytes;
public String status;
public String error;
public DownloadTask(String url, String outputPath) {
this.url = url;
this.outputPath = outputPath;
this.checksumAlgorithm = "SHA-256";
this.downloadedBytes = 0;
this.totalBytes = 0;
this.status = "pending";
}
}
public FileDownloader() {
// Default constructor for picocli
}
public FileDownloader(String outputDir, int chunkSize, int timeoutSeconds,
int maxRetries, int maxWorkers) {
this.outputDirStr = outputDir;
this.chunkSize = chunkSize;
this.timeoutSeconds = timeoutSeconds;
this.maxRetries = maxRetries;
this.workerCount = maxWorkers;
init();
}
private void init() {
this.outputDir = Paths.get(outputDirStr);
try {
Files.createDirectories(this.outputDir);
} catch (IOException e) {
throw new RuntimeException("Failed to create output directory: " + outputDirStr, e);
}
this.client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(timeoutSeconds))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
this.mapper = new ObjectMapper();
this.executor = Executors.newFixedThreadPool(workerCount);
}
/**
* Get the remote file size via HEAD request.
*/
private long getFileSize(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.method("HEAD", HttpRequest.BodyPublishers.noBody())
.timeout(Duration.ofSeconds(timeoutSeconds))
.build();
HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
if (response.statusCode() >= 200 && response.statusCode() < 300) {
Optional<String> contentLength = response.headers().firstValue("Content-Length");
return contentLength.map(Long::parseLong).orElse(-1L);
}
return -1;
}
/**
* Check if the server supports range requests.
*/
private boolean supportsRange(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.method("HEAD", HttpRequest.BodyPublishers.noBody())
.timeout(Duration.ofSeconds(timeoutSeconds))
.build();
HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
Optional<String> acceptRanges = response.headers().firstValue("Accept-Ranges");
return acceptRanges.isPresent() && !"none".equalsIgnoreCase(acceptRanges.get());
}
/**
* Compute the checksum of a file.
*/
private String computeChecksum(String filepath, String algorithm)
throws IOException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(algorithm);
try (InputStream is = new BufferedInputStream(new FileInputStream(filepath))) {
byte[] buffer = new byte[chunkSize];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
}
byte[] hashBytes = digest.digest();
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* Print a progress bar to the console.
*/
private synchronized void printProgress(String filename, long downloaded, long total) {
if (total <= 0) {
System.out.printf("\r%s: %s downloaded",
filename, formatBytes(downloaded));
return;
}
double percentage = (double) downloaded / total * 100;
int filled = (int) (PROGRESS_BAR_WIDTH * downloaded / total);
StringBuilder bar = new StringBuilder("[");
for (int i = 0; i < PROGRESS_BAR_WIDTH; i++) {
bar.append(i < filled ? "=" : (i == filled ? ">" : " "));
}
bar.append("]");
System.out.printf("\r%s: %s %5.1f%% %s/%s",
filename, bar, percentage,
formatBytes(downloaded), formatBytes(total));
}
/**
* Format bytes to human-readable string.
*/
private String formatBytes(long bytes) {
if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024));
String[] units = {"KB", "MB", "GB", "TB"};
return String.format("%.1f %s", bytes / Math.pow(1024, exp), units[exp - 1]);
}
/**
* Determine the output file path from a URL.
*/
private String getOutputPath(String url, String outputPath) {
if (outputPath != null && !outputPath.isEmpty()) {
Path path = Paths.get(outputPath);
if (!path.isAbsolute()) {
path = outputDir.resolve(path);
}
return path.toString();
}
String filename = url.substring(url.lastIndexOf('/') + 1).split("\\?")[0];
if (filename.isEmpty()) filename = "downloaded_file";
return outputDir.resolve(filename).toString();
}
/**
* Download a single file with progress and optional resume.
*/
public DownloadTask downloadFile(String url, String outputPath,
String expectedChecksum, String checksumAlgorithm,
boolean resume, boolean showProgress) {
DownloadTask task = new DownloadTask(url, getOutputPath(url, outputPath));
if (expectedChecksum != null) task.expectedChecksum = expectedChecksum;
if (checksumAlgorithm != null) task.checksumAlgorithm = checksumAlgorithm;
int attempts = 0;
while (attempts < maxRetries) {
attempts++;
try {
long totalSize = getFileSize(url);
task.totalBytes = totalSize;
File outFile = new File(task.outputPath);
outFile.getParentFile().mkdirs();
// Handle resume
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(timeoutSeconds));
boolean appending = false;
if (resume && outFile.exists()) {
long existingSize = outFile.length();
if (totalSize > 0 && existingSize == totalSize) {
task.downloadedBytes = existingSize;
task.status = "completed";
if (expectedChecksum != null) {
String actual = computeChecksum(task.outputPath, task.checksumAlgorithm);
if (!actual.equalsIgnoreCase(expectedChecksum)) {
task.status = "checksum_failed";
task.error = "Checksum verification failed";
}
}
return task;
}
if (totalSize > 0 && existingSize < totalSize && supportsRange(url)) {
requestBuilder.header("Range", "bytes=" + existingSize + "-");
appending = true;
task.downloadedBytes = existingSize;
}
}
HttpRequest request = requestBuilder.GET().build();
HttpResponse<InputStream> response = client.send(
request, HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode();
if (statusCode < 200 || statusCode >= 300) {
throw new IOException("HTTP error: " + statusCode);
}
if (totalSize < 0) {
Optional<String> cl = response.headers().firstValue("Content-Length");
if (cl.isPresent()) totalSize = Long.parseLong(cl.get());
task.totalBytes = totalSize;
}
task.status = "downloading";
String filename = outFile.getName();
try (InputStream is = response.body();
OutputStream os = appending
? Files.newOutputStream(outFile.toPath(), StandardOpenOption.APPEND)
: Files.newOutputStream(outFile.toPath())) {
byte[] buffer = new byte[chunkSize];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
task.downloadedBytes += bytesRead;
if (showProgress) {
printProgress(filename, task.downloadedBytes, task.totalBytes);
}
}
}
if (showProgress) System.out.println();
task.status = "completed";
// Verify checksum
if (expectedChecksum != null) {
String actual = computeChecksum(task.outputPath, task.checksumAlgorithm);
if (!actual.equalsIgnoreCase(expectedChecksum)) {
task.status = "checksum_failed";
task.error = "Checksum verification failed (expected: "
+ expectedChecksum + ", got: " + actual + ")";
}
}
return task;
} catch (Exception e) {
task.error = e.getMessage();
if (attempts >= maxRetries) {
task.status = "failed";
return task;
}
try {
Thread.sleep((long) (500 * Math.pow(2, attempts - 1)));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
task.status = "failed";
return task;
}
}
}
return task;
}
/**
* Download multiple files concurrently.
*/
public List<DownloadTask> downloadMultiple(List<String> urls, boolean resume, boolean showProgress) {
List<Future<DownloadTask>> futures = new ArrayList<>();
for (String url : urls) {
futures.add(executor.submit(() ->
downloadFile(url, null, null, null, resume, showProgress)));
}
List<DownloadTask> results = new ArrayList<>();
for (Future<DownloadTask> future : futures) {
try {
results.add(future.get());
} catch (InterruptedException | ExecutionException e) {
DownloadTask failed = new DownloadTask("unknown", "unknown");
failed.status = "failed";
failed.error = e.getMessage();
results.add(failed);
}
}
return results;
}
/**
* Download files from a JSON configuration.
*/
public List<DownloadTask> downloadFromJson(String jsonFilePath) throws IOException {
JsonNode root = mapper.readTree(new File(jsonFilePath));
List<Future<DownloadTask>> futures = new ArrayList<>();
if (root.isArray()) {
for (JsonNode item : root) {
String url = item.path("url").asText("");
String output = item.path("outputPath").asText(null);
String checksum = item.path("checksum").asText(null);
String algo = item.path("algorithm").asText("SHA-256");
futures.add(executor.submit(() ->
downloadFile(url, output, checksum, algo, true, true)));
}
}
List<DownloadTask> results = new ArrayList<>();
for (Future<DownloadTask> future : futures) {
try {
results.add(future.get());
} catch (InterruptedException | ExecutionException e) {
DownloadTask failed = new DownloadTask("unknown", "unknown");
failed.status = "failed";
failed.error = e.getMessage();
results.add(failed);
}
}
return results;
}
/**
* Export download results as JSON.
*/
public String toJson(List<DownloadTask> tasks) throws IOException {
ArrayNode array = mapper.createArrayNode();
for (DownloadTask task : tasks) {
ObjectNode node = mapper.createObjectNode();
node.put("url", task.url);
node.put("outputPath", task.outputPath);
node.put("status", task.status);
node.put("downloadedBytes", task.downloadedBytes);
node.put("totalBytes", task.totalBytes);
if (task.error != null) node.put("error", task.error);
array.add(node);
}
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(array);
}
@Override
public void close() {
if (executor != null) {
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
@Override
public Integer call() {
init();
try {
List<DownloadTask> tasks;
boolean resume = !noResume;
boolean showProgress = !noProgress;
if (jsonFile != null) {
tasks = downloadFromJson(jsonFile);
} else if (urls.size() == 1) {
DownloadTask task = downloadFile(
urls.get(0), null, checksum, algorithm, resume, showProgress);
tasks = Collections.singletonList(task);
} else {
tasks = downloadMultiple(urls, resume, showProgress);
}
// Print summary
System.out.println("\n--- Download Summary ---");
int failed = 0;
for (DownloadTask task : tasks) {
String icon = "completed".equals(task.status) ? "OK" : "FAIL";
System.out.printf("[%s] %s%n", icon, task.url);
System.out.printf(" -> %s%n", task.outputPath);
System.out.printf(" Status: %s%n", task.status);
if (task.error != null) {
System.out.printf(" Error: %s%n", task.error);
}
System.out.printf(" Size: %,d bytes%n", task.downloadedBytes);
if (!"completed".equals(task.status)) failed++;
}
if (failed > 0) {
System.out.printf("%n%d download(s) failed.%n", failed);
return 1;
} else {
System.out.printf("%nAll %d download(s) completed successfully.%n", tasks.size());
return 0;
}
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
return 1;
} finally {
close();
}
}
public static void main(String[] args) {
int exitCode = new CommandLine(new FileDownloader()).execute(args);
System.exit(exitCode);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.downloader</groupId>
<artifactId>http-file-downloader</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>HTTP File Downloader</name>
<description>HTTP file downloader with progress bars, resumable downloads, concurrent transfers, and checksum verification.</description>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<picocli.version>4.7.5</picocli.version>
<jackson.version>2.16.1</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>${picocli.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
<annotationProcessorPaths>
<path>
<groupId>info.picocli</groupId>
<artifactId>picocli-codegen</artifactId>
<version>${picocli.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.downloader.FileDownloader</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.downloader.FileDownloader</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
README.md
# HTTP File Downloader (Java - Trial 3) An HTTP file downloader featuring progress bars, resumable downloads, concurrent transfers, and checksum verification. ## Dependencies - **java.net.http** (built-in) - Modern Java HTTP client with HTTP/2 support (Java 11+) - **picocli** (4.7.5) - Annotation-based command-line interface framework - **Jackson** (2.16.1) - JSON parsing for configuration files and result serialization ## Setup ```bash mvn clean package ``` ## Usage ### Download a single file ```bash java -jar target/http-file-downloader-1.0.0.jar https://example.com/file.zip ``` ### Download multiple files concurrently ```bash java -jar target/http-file-downloader-1.0.0.jar https://example.com/file1.zip https://example.com/file2.zip -w 4 ``` ### Download with checksum verification ```bash java -jar target/http-file-downloader-1.0.0.jar https://example.com/file.zip --checksum abc123 --algorithm SHA-256 ``` ### Download from JSON config ```bash java -jar target/http-file-downloader-1.0.0.jar --json config.json ``` ### Show help ```bash java -jar target/http-file-downloader-1.0.0.jar --help ``` ## Features - **Progress Bars**: Console-based progress display with percentage, speed, and size info - **Resumable Downloads**: Resumes interrupted downloads using HTTP Range headers - **Concurrent Transfers**: Thread pool-based parallel downloads with configurable worker count - **Checksum Verification**: Verify downloads against SHA-256, SHA-1, or MD5 checksums - **JSON Configuration**: Load download tasks from JSON config files via Jackson - **Picocli CLI**: Full-featured CLI with help text, version info, and argument validation - **Retry Logic**: Automatic retry with exponential backoff on failures - **Built-in HTTP Client**: Uses Java's modern java.net.http client with HTTP/2 support