Skip to main content

Module 03: ClaudeSyncClient

Multi-turn conversations with context preservation.

What You’ll Learn

  • Creating clients with ClaudeClient.sync()
  • Multi-turn conversations where Claude remembers context
  • Simple text responses with connectText() and queryText()
  • Full message access with connectAndReceive()

Query vs ClaudeSyncClient

FeatureQueryClaudeSyncClient
ContextNew session each callPreserved across calls
Use caseOne-off questionsConversations
LifecycleAutomaticManual (try-with-resources)
Follow-upsNot supportedFully supported
Hooks/MCPNot supportedSupported

Multi-Turn Conversation (Simple)

For most use cases, you just want the text response:
import org.springaicommunity.claude.agent.sdk.ClaudeClient;
import org.springaicommunity.claude.agent.sdk.ClaudeSyncClient;
import java.nio.file.Path;

try (ClaudeSyncClient client = ClaudeClient.sync()
        .workingDirectory(Path.of("."))
        .build()) {

    String answer1 = client.connectText("What's the capital of France?");
    System.out.println(answer1);  // "Paris"

    String answer2 = client.queryText("What's the population of that city?");
    System.out.println(answer2);  // Claude remembers "Paris"

    String answer3 = client.queryText("What are its famous landmarks?");
    System.out.println(answer3);
}

Multi-Turn with Message Access

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

try (ClaudeSyncClient client = ClaudeClient.sync()
        .workingDirectory(Path.of("."))
        .build()) {

    for (Message msg : client.connectAndReceive("List files in current directory")) {
        System.out.println(msg);  // All message 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());
        }
    }
}

Key Points

  • Use try-with-resources: Clients hold resources that must be released
  • Factory pattern: Use ClaudeClient.sync() to create clients
  • Text methods for 80% use case: connectText() and queryText() return String
  • Message iteration for 20% use case: connectAndReceive() returns Iterable<Message>
  • Good toString(): All message types print useful information

ClaudeSyncClient Methods

MethodReturnsDescription
connectText(prompt)StringStart session, return text response
queryText(prompt)StringFollow-up, return text response
connectAndReceive(prompt)Iterable<Message>Start session, iterate all messages
queryAndReceive(prompt)Iterable<Message>Follow-up, iterate all messages
interrupt()voidStop current operation
close()voidEnd session (called by try-with-resources)

Builder Options

ClaudeClient.sync()
    .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();

Source Code

View on GitHub

Running the Example

mvn compile exec:java -pl module-03-sync-client

Next Module

Module 04: ClaudeAsyncClient - Reactive, composable, non-blocking chains.