Skip to main content

Module 04: ClaudeAsyncClient

Reactive, composable, non-blocking chains with Project Reactor.

What You’ll Learn

  • Creating clients with ClaudeClient.async()
  • The TurnSpec pattern for response handling
  • Multi-turn conversations with flatMap chaining
  • When to choose async vs sync clients

ClaudeSyncClient vs ClaudeAsyncClient

Both clients provide identical capabilities. They differ only in programming paradigm:
FeatureClaudeSyncClientClaudeAsyncClient
StyleBlocking, sequentialReactive, composable
ReturnsString, Iterable<Message>TurnSpecMono/Flux
Multi-turnSupportedSupported
Hooks/MCPSupportedSupported
Best forSimple scripts, CLI toolsReactive apps, composition

TurnSpec: Response Handling

ClaudeAsyncClient returns a TurnSpec that provides three ways to handle responses:
MethodReturnsUse Case
.text()Mono<String>Collected text, enables flatMap chaining
.textStream()Flux<String>Streaming text as it arrives
.messages()Flux<Message>All message types for metadata access

Multi-Turn Conversation

The .text() method returns Mono<String>, enabling elegant flatMap chaining:
import org.springaicommunity.claude.agent.sdk.ClaudeClient;
import org.springaicommunity.claude.agent.sdk.ClaudeAsyncClient;
import java.nio.file.Path;

ClaudeAsyncClient client = ClaudeClient.async()
    .workingDirectory(Path.of("."))
    .build();

// Multi-turn with elegant flatMap chaining
client.connect("What's the capital of France?").text()
    .doOnSuccess(System.out::println)  // "Paris"
    .flatMap(r1 -> client.query("What's the population of that city?").text())
    .doOnSuccess(System.out::println)  // Claude remembers "Paris"
    .flatMap(r2 -> client.query("What are its famous landmarks?").text())
    .doOnSuccess(System.out::println)
    .subscribe();  // Non-blocking

Text Streaming

For streaming text as it arrives:
client.query("Explain recursion").textStream()
    .doOnNext(System.out::print)
    .subscribe();

Full Message Access

When you need metadata, tool use details, or cost information:
import org.springaicommunity.claude.agent.sdk.types.Message;
import org.springaicommunity.claude.agent.sdk.types.AssistantMessage;
import org.springaicommunity.claude.agent.sdk.types.ResultMessage;

client.query("List files").messages()
    .doOnNext(msg -> {
        System.out.println(msg);  // All types have useful toString()

        if (msg instanceof AssistantMessage am) {
            am.getToolUses().forEach(tool ->
                System.out.println("Tool used: " + tool.name()));
        } else if (msg instanceof ResultMessage rm) {
            System.out.printf("Cost: $%.6f%n", rm.totalCostUsd());
        }
    })
    .subscribe();

Key Points

  • Factory pattern: Use ClaudeClient.async() to create clients
  • TurnSpec pattern: connect()/query() return TurnSpec, not void
  • flatMap for multi-turn: Chain turns with .text().flatMap(r -> ...)
  • Non-blocking: Use .subscribe(), avoid .block()
  • Same capabilities: Hooks, MCP, permissions all work identically to sync

ClaudeAsyncClient Methods

MethodReturnsDescription
connect(prompt)TurnSpecStart session with prompt
query(prompt)TurnSpecSend follow-up message
onMessage(handler)ClaudeAsyncClientRegister cross-turn message handler
onResult(handler)ClaudeAsyncClientRegister cross-turn result handler
close()Mono<Void>End session

TurnSpec Methods

MethodReturnsDescription
.text()Mono<String>Collected text response
.textStream()Flux<String>Streaming text
.messages()Flux<Message>All message types

Builder Options

ClaudeClient.async()
    .workingDirectory(Path.of("."))       // Required
    .model("claude-sonnet-4-20250514")    // Model selection
    .appendSystemPrompt("Be concise")      // Add to default prompt (recommended)
    .timeout(Duration.ofMinutes(5))        // Timeout
    .permissionMode(PermissionMode.DEFAULT) // Permission handling
    .hookRegistry(hookRegistry)            // Tool hooks
    .mcpServer("name", config)             // MCP servers
    .build();

When to Choose

ScenarioRecommendation
Simple script or CLIClaudeSyncClient
Traditional blocking appClaudeSyncClient
Simpler debuggingClaudeSyncClient
Reactive applicationClaudeAsyncClient
Composing with other reactive streamsClaudeAsyncClient
High-concurrency serverClaudeAsyncClient

Source Code

View on GitHub

Running the Example

mvn compile exec:java -pl module-04-async-client

Next Module

Module 05: Message Types - Understanding the different message types and content blocks.