← All tasks
javaclaude-code/java-t3 #24Verified, not scored yet

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