C#
JV

C# to Java

10 lessons

Progress0%
1Introduction2Type Systems3Properties & Getters4Generics5Collections6LINQ to Streams7Async Programming8Ecosystem9Modern Java Features10Concurrency
All Mirror Courses
C#
JV
Async Programming
MirrorLesson 7 of 10
Lesson 7

Async Programming

Async Programming

Introduction

In this lesson, you'll learn about async programming in Java. Coming from C#, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
C#
From C#:

In C#, you're familiar with async programming.

JV
In Java:

Java has its own approach to async programming, which we'll explore step by step.

The Java Way

Let's see how Java handles this concept. Here's a typical example:

JV
Java Example
CompletableFuture<String> fetchAsync() {
    return CompletableFuture.supplyAsync(() ->
        httpClient.send(request, BodyHandlers.ofString()).body()
    );
}

// Chaining
CompletableFuture<Void> processAsync() {
    return fetchAsync()
        .thenCompose(data -> parseAsync(data))
        .thenAccept(parsed -> System.out.println(parsed));
}

Comparing to C#

Here's how you might have written similar code in C#:

C#
C# (What you know)
async Task<string> FetchAsync() {
    var result = await httpClient.GetStringAsync(url);
    return result;
}

// Chaining
async Task ProcessAsync() {
    var data = await FetchAsync();
    var parsed = await ParseAsync(data);
    Console.WriteLine(parsed);
}
Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

JV
In Java:

C# async/await is built into the language; Java uses CompletableFuture API

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

JV
In Java:

Task<T> (C#) maps to CompletableFuture<T> (Java)

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

JV
In Java:

await in C# maps to .thenApply()/.thenCompose() chains in Java

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

JV
In Java:

Both are non-blocking on the calling thread

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

JV
In Java:

Java 21 virtual threads (Project Loom) bring async-like simplicity with blocking-style code

Step-by-Step Breakdown

1. Task vs CompletableFuture

C# Task<T> and Java CompletableFuture<T> both represent a future value. Creating one differs: C# uses the async keyword, Java uses supplyAsync or similar factory methods.

C#
C#
async Task<int> ComputeAsync() {
    await Task.Delay(100);
    return 42;
}
JV
Java
CompletableFuture<Integer> computeAsync() {
    return CompletableFuture.supplyAsync(() -> {
        Thread.sleep(100); // checked exception — wrap it
        return 42;
    });
}
Common Pitfall
Java lambdas cannot throw checked exceptions. Wrap them in a try/catch or use a helper that wraps checked exceptions into RuntimeExceptions.

2. Chaining Operations

await in C# suspends and resumes inline. Java uses method chains: thenApply (sync transform), thenCompose (async flatMap), thenAccept (terminal).

C#
C#
var data   = await FetchAsync();
var parsed = await ParseAsync(data);
Display(parsed);
JV
Java
fetchAsync()
    .thenCompose(data -> parseAsync(data)) // async step
    .thenAccept(parsed -> display(parsed)); // terminal
Rule of Thumb
thenApply is like Select/map. thenCompose is like SelectMany/flatMap for when the next step also returns a CompletableFuture.

3. Error Handling

C# uses try/catch around awaited code. Java chains .exceptionally() or .handle() on the CompletableFuture.

C#
C#
try {
    var result = await FetchAsync();
} catch (HttpRequestException ex) {
    Console.WriteLine($"Error: {ex.Message}");
}
JV
Java
fetchAsync()
    .exceptionally(ex -> {
        System.out.println("Error: " + ex.getMessage());
        return "fallback";
    })
    .thenAccept(result -> System.out.println(result));

4. Virtual Threads (Java 21+)

Java 21 Project Loom introduces virtual threads. You can write blocking-style code (like C# sync code) and the JVM handles scheduling efficiently — no CompletableFuture chains needed.

C#
C#
// C# — async/await for non-blocking I/O
async Task<string> FetchAsync() {
    return await httpClient.GetStringAsync(url);
}
JV
Java
// Java 21+ virtual thread — blocking but scalable!
String fetch() {
    // Run on a virtual thread; blocks the vthread, not OS thread
    return httpClient.send(request, BodyHandlers.ofString()).body();
}

// Start on a virtual thread
Thread.ofVirtual().start(() -> {
    String result = fetch();
    System.out.println(result);
});
Rule of Thumb
For new Java 21+ projects, prefer virtual threads with blocking code over CompletableFuture chains — it's simpler and equally scalable.

Common Mistakes

When coming from C#, developers often make these mistakes:

  • C# async/await is built into the language; Java uses CompletableFuture API
  • Task<T> (C#) maps to CompletableFuture<T> (Java)
  • await in C# maps to .thenApply()/.thenCompose() chains in Java
Common Pitfall
Don't assume Java works exactly like C#. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • Task<T> → CompletableFuture<T>; await → .thenCompose()/.thenApply()
  • Use exceptionally() for error handling in chains
  • Java 21 virtual threads enable blocking-style async code without callbacks
  • Both models are non-blocking on the underlying OS thread
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your C# code in Java to practice these concepts.
PreviousNext