Lesson 7 of 744 minModule progress 0%

Module 15: Advanced Concurrency and Async Design

Mini-Project: Concurrent Download Manager

Build a realistic concurrent download manager that queues work, tracks progress, limits concurrency, and reports failures clearly. The goal is not only to “use threads,” but to design a small system that stays understandable under parallel load.

Author

Java Learner Editorial Team

Reviewer

Technical review by Java Learner

Last reviewed

2026-04-17

Java version

Java 25 LTS

How this lesson was prepared: AI-assisted draft, manually expanded into a full lesson guide, and checked against current official Java, Spring, testing, and delivery documentation.

Learning goals

  • Design a bounded concurrent workflow with clear task ownership
  • Track progress, failure, and cancellation without corrupting shared state
  • Practice the kind of design trade-offs that appear in production async systems

Before you start

  • You are comfortable with classes, methods, exceptions, and collections

Lesson roadmap: Start with the mental model, then follow the design choices, common pitfalls, and the practical workflow you should apply in a real project.

Project goal: Simulate or implement parallel file downloads with a bounded worker pool and per-task progress updates.

Core design pieces: Executor service, thread-safe progress state, a cancellation flag or future cancellation, and clear task boundaries.

Stretch ideas: Add retry limits, queued tasks, download priority, or a summary report when all jobs finish.

Success check: The program should stay correct when several downloads update progress at the same time.

Project scope: Model downloads as tasks with states such as queued, running, completed, failed, and cancelled. The state model is just as important as the concurrent execution model.

Suggested architecture: One request layer submits tasks, one executor runs them, one thread-safe progress store reports status, and one retry policy decides what to do with transient failures.

Milestone plan: Start with sequential downloads, then add a fixed executor, then add safe progress tracking, then add cancellation and retries. Building in slices keeps the project debuggable.

Professional finish: Add structured logs, thread names, graceful shutdown, and a small README that explains the concurrency choices you made.

How to study this module: Run each example more than once, print the current thread name, and change the workload size. Concurrency concepts only become real when you can see scheduling, waiting, and shared-state behavior.

Code review mindset: In concurrent code, correctness comes before throughput. First ask who owns the data, who can mutate it, and what guarantees make each read or write safe.

Production habit: Prefer small, explicit concurrency boundaries. Immutable data, bounded executors, cancellation support, and clear logging are easier to maintain than clever low-level tricks.

Runnable examples

Track download count safely

import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger completed = new AtomicInteger(0);
completed.incrementAndGet();
System.out.println("Completed: " + completed.get());

Expected output

Completed: 1

Represent download state explicitly

enum DownloadStatus {
    QUEUED,
    RUNNING,
    COMPLETED,
    FAILED,
    CANCELLED
}

Expected output

Explicit states make progress reporting and debugging much easier.

Common mistakes

Mixing task execution, status storage, retry rules, and console output in one giant class

Split responsibilities early so each piece is easier to test and reason about under concurrency.

Updating shared progress from many threads without a clear owner or safe structure

Use thread-safe counters, guarded maps, or message-passing style updates instead of ad-hoc shared mutation.

Mini exercise

Before coding, write the lifecycle of one download from submission to completion. Include the exact places where the task can fail, be cancelled, or be retried, and how the status should change in each case.

Summary

  • The project combines executors, shared state, and coordination.
  • Progress tracking must be thread-safe.
  • Concurrency design is about correctness under overlap, not just about running many things at once.
  • A good concurrency project is mostly about state design, task ownership, and failure handling.
  • Build the system in vertical slices so each step stays testable and explainable.

Next step

The next module moves from in-memory coordination to database-backed systems, where correctness depends on resource management and transaction boundaries.

Sources used

Advertisement

Lesson check

What is the most important design concern in the download manager project?

Next lesson →