# SkillPatch skill: azure-communication-callautomation-java

This skill provides comprehensive guidance for building server-side call automation workflows using the Azure Communication Services Call Automation Java SDK. It covers client creation, outbound/inbound call handling, IVR systems, DTMF recognition, text-to-speech, call recording, and AI-powered call flows. Agents can follow the included code examples and configuration patterns to implement real telephony automation in Java applications.

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/azure-communication-callautomation-java
curl -sSL https://skillpatch.dev/install_skill/azure-communication-callautomation-java | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (2)

- `SKILL.md`
- `references/examples.md`


### `SKILL.md`

````markdown
---
name: azure-communication-callautomation-java
description: Build call automation workflows with Azure Communication Services Call Automation Java SDK. Use when implementing IVR systems, call routing, call recording, DTMF recognition, text-to-speech, or AI-powered call flows.
license: MIT
metadata:
  author: Microsoft
  version: "1.0.0"
  package: com.azure:azure-communication-callautomation
---

# Azure Communication Call Automation (Java)

Build server-side call automation workflows including IVR systems, call routing, recording, and AI-powered interactions.

## Installation

```xml
<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-communication-callautomation</artifactId>
    <version>1.6.0</version>
</dependency>
```

## Client Creation

```java
import com.azure.communication.callautomation.CallAutomationClient;
import com.azure.communication.callautomation.CallAutomationClientBuilder;
import com.azure.core.credential.TokenCredential;
import com.azure.identity.AzureIdentityEnvVars;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.identity.ManagedIdentityCredentialBuilder;

// Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
TokenCredential credential = new DefaultAzureCredentialBuilder()
    .requireEnvVars(AzureIdentityEnvVars.AZURE_TOKEN_CREDENTIALS)
    .build();
// Or use a specific credential directly in production:
// See https://learn.microsoft.com/java/api/overview/azure/identity-readme?view=azure-java-stable#credential-classes
// TokenCredential credential = new ManagedIdentityCredentialBuilder().build();

// With DefaultAzureCredential
CallAutomationClient client = new CallAutomationClientBuilder()
    .endpoint("https://<resource>.communication.azure.com")
    .credential(credential)
    .buildClient();

// With connection string
CallAutomationClient client = new CallAutomationClientBuilder()
    .connectionString("<connection-string>")
    .buildClient();
```

## Key Concepts

| Class | Purpose |
|-------|---------|
| `CallAutomationClient` | Make calls, answer/reject incoming calls, redirect calls |
| `CallConnection` | Actions in established calls (add participants, terminate) |
| `CallMedia` | Media operations (play audio, recognize DTMF/speech) |
| `CallRecording` | Start/stop/pause recording |
| `CallAutomationEventParser` | Parse webhook events from ACS |

## Create Outbound Call

```java
import com.azure.communication.callautomation.models.*;
import com.azure.communication.common.CommunicationUserIdentifier;
import com.azure.communication.common.PhoneNumberIdentifier;

// Call to PSTN number
PhoneNumberIdentifier target = new PhoneNumberIdentifier("+14255551234");
PhoneNumberIdentifier caller = new PhoneNumberIdentifier("+14255550100");

CreateCallOptions options = new CreateCallOptions(
    new CommunicationUserIdentifier("<user-id>"),  // Source
    List.of(target))                                // Targets
    .setSourceCallerId(caller)
    .setCallbackUrl("https://your-app.com/api/callbacks");

CreateCallResult result = client.createCall(options);
String callConnectionId = result.getCallConnectionProperties().getCallConnectionId();
```

## Answer Incoming Call

```java
// From Event Grid webhook - IncomingCall event
String incomingCallContext = "<incoming-call-context-from-event>";

AnswerCallOptions options = new AnswerCallOptions(
    incomingCallContext,
    "https://your-app.com/api/callbacks");

AnswerCallResult result = client.answerCall(options);
CallConnection callConnection = result.getCallConnection();
```

## Play Audio (Text-to-Speech)

```java
CallConnection callConnection = client.getCallConnection(callConnectionId);
CallMedia callMedia = callConnection.getCallMedia();

// Play text-to-speech
TextSource textSource = new TextSource()
    .setText("Welcome to Contoso. Press 1 for sales, 2 for support.")
    .setVoiceName("en-US-JennyNeural");

PlayOptions playOptions = new PlayOptions(
    List.of(textSource),
    List.of(new CommunicationUserIdentifier("<target-user>")));

callMedia.play(playOptions);

// Play audio file
FileSource fileSource = new FileSource()
    .setUrl("https://storage.blob.core.windows.net/audio/greeting.wav");

callMedia.play(new PlayOptions(List.of(fileSource), List.of(target)));
```

## Recognize DTMF Input

```java
// Recognize DTMF tones
DtmfTone stopTones = DtmfTone.POUND;

CallMediaRecognizeDtmfOptions recognizeOptions = new CallMediaRecognizeDtmfOptions(
    new CommunicationUserIdentifier("<target-user>"),
    5)  // Max tones to collect
    .setInterToneTimeout(Duration.ofSeconds(5))
    .setStopTones(List.of(stopTones))
    .setInitialSilenceTimeout(Duration.ofSeconds(15))
    .setPlayPrompt(new TextSource().setText("Enter your account number followed by pound."));

callMedia.startRecognizing(recognizeOptions);
```

## Recognize Speech

```java
// Speech recognition with AI
CallMediaRecognizeSpeechOptions speechOptions = new CallMediaRecognizeSpeechOptions(
    new CommunicationUserIdentifier("<target-user>"))
    .setEndSilenceTimeout(Duration.ofSeconds(2))
    .setSpeechLanguage("en-US")
    .setPlayPrompt(new TextSource().setText("How can I help you today?"));

callMedia.startRecognizing(speechOptions);
```

## Call Recording

```java
CallRecording callRecording = client.getCallRecording();

// Start recording
StartRecordingOptions recordingOptions = new StartRecordingOptions(
    new ServerCallLocator("<server-call-id>"))
    .setRecordingChannel(RecordingChannel.MIXED)
    .setRecordingContent(RecordingContent.AUDIO_VIDEO)
    .setRecordingFormat(RecordingFormat.MP4);

RecordingStateResult recordingResult = callRecording.start(recordingOptions);
String recordingId = recordingResult.getRecordingId();

// Pause/resume/stop
callRecording.pause(recordingId);
callRecording.resume(recordingId);
callRecording.stop(recordingId);

// Download recording (after RecordingFileStatusUpdated event)
callRecording.downloadTo(recordingUrl, Paths.get("recording.mp4"));
```

## Add Participant to Call

```java
CallConnection callConnection = client.getCallConnection(callConnectionId);

CommunicationUserIdentifier participant = new CommunicationUserIdentifier("<user-id>");
AddParticipantOptions addOptions = new AddParticipantOptions(participant)
    .setInvitationTimeout(Duration.ofSeconds(30));

AddParticipantResult result = callConnection.addParticipant(addOptions);
```

## Transfer Call

```java
// Blind transfer
PhoneNumberIdentifier transferTarget = new PhoneNumberIdentifier("+14255559999");
TransferCallToParticipantResult result = callConnection.transferCallToParticipant(transferTarget);
```

## Handle Events (Webhook)

```java
import com.azure.communication.callautomation.CallAutomationEventParser;
import com.azure.communication.callautomation.models.events.*;

// In your webhook endpoint
public void handleCallback(String requestBody) {
    List<CallAutomationEventBase> events = CallAutomationEventParser.parseEvents(requestBody);
    
    for (CallAutomationEventBase event : events) {
        if (event instanceof CallConnected) {
            CallConnected connected = (CallConnected) event;
            System.out.println("Call connected: " + connected.getCallConnectionId());
        } else if (event instanceof RecognizeCompleted) {
            RecognizeCompleted recognized = (RecognizeCompleted) event;
            // Handle DTMF or speech recognition result
            DtmfResult dtmfResult = (DtmfResult) recognized.getRecognizeResult();
            String tones = dtmfResult.getTones().stream()
                .map(DtmfTone::toString)
                .collect(Collectors.joining());
            System.out.println("DTMF received: " + tones);
        } else if (event instanceof PlayCompleted) {
            System.out.println("Audio playback completed");
        } else if (event instanceof CallDisconnected) {
            System.out.println("Call ended");
        }
    }
}
```

## Hang Up Call

```java
// Hang up for all participants
callConnection.hangUp(true);

// Hang up only this leg
callConnection.hangUp(false);
```

## Error Handling

```java
import com.azure.core.exception.HttpResponseException;

try {
    client.answerCall(options);
} catch (HttpResponseException e) {
    if (e.getResponse().getStatusCode() == 404) {
        System.out.println("Call not found or already ended");
    } else if (e.getResponse().getStatusCode() == 400) {
        System.out.println("Invalid request: " + e.getMessage());
    }
}
```

## Environment Variables

```bash
AZURE_COMMUNICATION_ENDPOINT=https://<resource>.communication.azure.com  # Required for all auth methods
AZURE_COMMUNICATION_CONNECTION_STRING=endpoint=https://...;accesskey=...  # Alternative to Entra ID auth
CALLBACK_BASE_URL=https://your-app.com/api/callbacks  # Required for webhook callbacks
AZURE_TOKEN_CREDENTIALS=prod  # Required only if DefaultAzureCredential is used in production
```

## Trigger Phrases

- "call automation Java", "IVR Java", "interactive voice response"
- "call recording Java", "DTMF recognition Java"
- "text to speech call", "speech recognition call"
- "answer incoming call", "transfer call Java"
- "Azure Communication Services call automation"

````


### `references/examples.md`

````markdown
# Azure Communication Call Automation SDK for Java - Examples

Comprehensive code examples for the Azure Communication Call Automation SDK for Java.

## Table of Contents
- [Maven Dependency](#maven-dependency)
- [Client Creation](#client-creation)
- [Creating Outbound Calls](#creating-outbound-calls)
- [Answering Inbound Calls](#answering-inbound-calls)
- [Playing Audio/TTS](#playing-audiotts)
- [Recognizing DTMF Tones](#recognizing-dtmf-tones)
- [Recording Calls](#recording-calls)
- [Transfer Calls](#transfer-calls)
- [Add/Remove Participants](#addremove-participants)
- [Handling Call Events](#handling-call-events)
- [Async Client Patterns](#async-client-patterns)
- [Error Handling](#error-handling)

## Maven Dependency

```xml
<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-communication-callautomation</artifactId>
    <version>1.5.2</version>
</dependency>
```

## Client Creation

### Sync Client

```java
import com.azure.communication.callautomation.CallAutomationClient;
import com.azure.communication.callautomation.CallAutomationClientBuilder;

CallAutomationClient callAutomationClient = new CallAutomationClientBuilder()
    .connectionString("<Azure Communication Services connection string>")
    .buildClient();
```

### Async Client

```java
import com.azure.communication.callautomation.CallAutomationAsyncClient;

CallAutomationAsyncClient asyncClient = new CallAutomationClientBuilder()
    .connectionString("<acsConnectionString>")
    .buildAsyncClient();
```

## Creating Outbound Calls

### Single PSTN Call

```java
import com.azure.communication.callautomation.models.*;
import com.azure.communication.common.PhoneNumberIdentifier;
import com.azure.core.util.Context;

String callbackUri = "https://<myendpoint>/Events";
PhoneNumberIdentifier callerIdNumber = new PhoneNumberIdentifier("+18001234567");
PhoneNumberIdentifier target = new PhoneNumberIdentifier("+16471234567");

CallInvite callInvite = new CallInvite(target, callerIdNumber);
CreateCallOptions createCallOptions = new CreateCallOptions(callInvite, callbackUri);

// Optional: Add AI capabilities
CallIntelligenceOptions callIntelligenceOptions = new CallIntelligenceOptions()
    .setCognitiveServicesEndpoint("https://your-cognitive-services.cognitiveservices.azure.com/");
createCallOptions.setCallIntelligenceOptions(callIntelligenceOptions);

Response<CreateCallResult> result = callAutomationClient.createCallWithResponse(
    createCallOptions, 
    Context.NONE
);
String callConnectionId = result.getValue().getCallConnectionProperties().getCallConnectionId();
```

### Group Call

```java
import com.azure.communication.common.CommunicationUserIdentifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

List<CommunicationIdentifier> targets = new ArrayList<>(Arrays.asList(
    new PhoneNumberIdentifier("+16471234567"),
    new CommunicationUserIdentifier("<user_id>")
));

CreateGroupCallOptions groupCallOptions = new CreateGroupCallOptions(targets, callbackUri);
groupCallOptions.setSourceCallIdNumber(callerIdNumber);

Response<CreateCallResult> response = callAutomationClient.createGroupCallWithResponse(
    groupCallOptions, 
    Context.NONE
);
```

## Answering Inbound Calls

### Basic Answer

```java
import com.azure.communication.callautomation.models.*;

String incomingCallContext = "<IncomingCallContext_From_IncomingCall_Event>";
String callbackUri = "https://<myendpoint>/Events";

AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, callbackUri);
Response<AnswerCallResult> response = callAutomationClient.answerCallWithResponse(
    answerCallOptions, 
    Context.NONE
);
```

### Answer with AI Capabilities

```java
CallIntelligenceOptions callIntelligenceOptions = new CallIntelligenceOptions()
    .setCognitiveServicesEndpoint("https://cognitive-services.cognitiveservices.azure.com/");

AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, callbackUri)
    .setCallIntelligenceOptions(callIntelligenceOptions);

Response<AnswerCallResult> response = callAutomationClient.answerCallWithResponse(
    answerCallOptions, 
    Context.NONE
);
```

### Answer with Transcription

```java
TranscriptionOptions transcriptionOptions = new TranscriptionOptions(
    "wss://your-websocket-url",
    TranscriptionTransport.WEBSOCKET,
    "en-US",
    false,
    "your-endpoint-id"
);

AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, callbackUri)
    .setCallIntelligenceOptions(callIntelligenceOptions)
    .setTranscriptionOptions(transcriptionOptions);
```

## Playing Audio/TTS

### Play Text-to-Speech to All

```java
import com.azure.communication.callautomation.CallConnection;

String textToPlay = "Welcome to Contoso. How can I help you today?";

TextSource textSource = new TextSource()
    .setText(textToPlay)
    .setVoiceName("en-US-NancyNeural");

CallConnection callConnection = callAutomationClient.getCallConnection(callConnectionId);
callConnection.getCallMedia().playToAll(textSource);
```

### Play Audio File to Participant

```java
import java.util.Arrays;

CommunicationIdentifier targetParticipant = new PhoneNumberIdentifier("+16471234567");

FileSource playSource = new FileSource()
    .setUrl("https://storage.blob.core.windows.net/audio/welcome.wav")
    .setPlaySourceCacheId("<playSourceId>");  // Optional: cache for repeated playback

var playTo = Arrays.asList(targetParticipant);
PlayOptions playOptions = new PlayOptions(playSource, playTo);

callAutomationClient.getCallConnection(callConnectionId)
    .getCallMedia()
    .playWithResponse(playOptions, Context.NONE);
```

### Play with SSML

```java
String ssmlText = "<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"en-US\">" +
    "<voice name=\"en-US-JennyNeural\">Hello, welcome to our service!</voice></speak>";

SsmlSource ssmlSource = new SsmlSource()
    .setSsmlText(ssmlText);

callConnection.getCallMedia().playToAll(ssmlSource);
```

## Recognizing DTMF Tones

### DTMF Recognition

```java
import java.time.Duration;
import java.util.Arrays;

CommunicationIdentifier targetParticipant = new PhoneNumberIdentifier("+16471234567");
int maxTonesToCollect = 3;

TextSource playSource = new TextSource()
    .setText("Please enter 3 digits.")
    .setVoiceName("en-US-ElizabethNeural");

CallMediaRecognizeDtmfOptions recognizeOptions = new CallMediaRecognizeDtmfOptions(
    targetParticipant, 
    maxTonesToCollect
)
    .setInitialSilenceTimeout(Duration.ofSeconds(30))
    .setPlayPrompt(playSource)
    .setInterToneTimeout(Duration.ofSeconds(5))
    .setInterruptPrompt(true)
    .setStopTones(Arrays.asList(DtmfTone.POUND));

callAutomationClient.getCallConnection(callConnectionId)
    .getCallMedia()
    .startRecognizingWithResponse(recognizeOptions, Context.NONE);
```

### Speech or DTMF Recognition

```java
CallMediaRecognizeSpeechOrDtmfOptions recognizeOptions = 
    new CallMediaRecognizeSpeechOrDtmfOptions(
        targetParticipant, 
        maxTonesToCollect, 
        Duration.ofMillis(1000)
    )
    .setPlayPrompt(playSource)
    .setInitialSilenceTimeout(Duration.ofSeconds(30))
    .setInterruptPrompt(true)
    .setOperationContext("OpenQuestionSpeechOrDtmf");

callAutomationClient.getCallConnection(callConnectionId)
    .getCallMedia()
    .startRecognizingWithResponse(recognizeOptions, Context.NONE);
```

### Choice Recognition

```java
import java.util.List;

List<RecognitionChoice> choices = Arrays.asList(
    new RecognitionChoice()
        .setLabel("Confirm")
        .setPhrases(Arrays.asList("Confirm", "Yes", "One"))
        .setTone(DtmfTone.ONE),
    new RecognitionChoice()
        .setLabel("Cancel")
        .setPhrases(Arrays.asList("Cancel", "No", "Two"))
        .setTone(DtmfTone.TWO)
);

TextSource playSource = new TextSource()
    .setText("Say Confirm or press 1 to confirm, or say Cancel or press 2 to cancel.")
    .setVoiceName("en-US-ElizabethNeural");

CallMediaRecognizeChoiceOptions recognizeOptions = new CallMediaRecognizeChoiceOptions(
    targetParticipant, 
    choices
)
    .setInterruptPrompt(true)
    .setInitialSilenceTimeout(Duration.ofSeconds(30))
    .setPlayPrompt(playSource)
    .setOperationContext("AppointmentReminderMenu");

callAutomationClient.getCallConnection(callConnectionId)
    .getCallMedia()
    .startRecognizingWithResponse(recognizeOptions, Context.NONE);
```

### Send DTMF Tones

```java
List<DtmfTone> tones = Arrays.asList(
    DtmfTone.ONE, 
    DtmfTone.TWO, 
    DtmfTone.THREE, 
    DtmfTone.POUND
);

SendDtmfTonesOptions options = new SendDtmfTonesOptions(
    tones, 
    new PhoneNumberIdentifier(targetPhoneNumber)
);
options.setOperationContext("dtmfs-to-ivr");

callAutomationClient.getCallConnection(callConnectionId)
    .getCallMedia()
    .sendDtmfTonesWithResponse(options, Context.NONE);
```

## Recording Calls

### Start Recording

```java
import com.azure.communication.callautomation.CallRecording;

CallRecording callRecording = callAutomationClient.getCallRecording();

StartRecordingOptions startRecordingOptions = new StartRecordingOptions(
    new ServerCallLocator(serverCallId)
)
    .setRecordingContent(RecordingContent.AUDIO_VIDEO)
    .setRecordingChannel(RecordingChannel.MIXED)
    .setRecordingFormat(RecordingFormat.MP4);

Response<RecordingStateResult> response = callRecording.startWithResponse(
    startRecordingOptions, 
    Context.NONE
);

String recordingId = response.getValue().getRecordingId();
```

### Pause/Resume Recording

```java
// Pause
callRecording.pause(recordingId);

// Resume
callRecording.resume(recordingId);
```

### Stop Recording

```java
callRecording.stop(recordingId);
```

### Download Recording

```java
callRecording.downloadToWithResponse(
    recordingUrl,
    Paths.get("recording.mp4"),
    null,  // HttpRange (optional)
    Context.NONE
);
```

## Transfer Calls

### Blind Transfer

```java
CommunicationIdentifier transferDestination = new PhoneNumberIdentifier("+16471234567");

TransferCallToParticipantOptions transferOptions = new TransferCallToParticipantOptions(
    transferDestination
);

Response<TransferCallResult> response = callAutomationClient
    .getCallConnection(callConnectionId)
    .transferCallToParticipantWithResponse(transferOptions, Context.NONE);
```

### Transfer with Announcement

```java
TextSource transferee Prompt = new TextSource()
    .setText("Please hold while we transfer your call.")
    .setVoiceName("en-US-NancyNeural");

TransferCallToParticipantOptions transferOptions = new TransferCallToParticipantOptions(
    transferDestination
)
    .setSourceCallerIdNumber(new PhoneNumberIdentifier("+18001234567"))
    .setTransfereeGreeting(transfereePrompt);

callAutomationClient.getCallConnection(callConnectionId)
    .transferCallToParticipantWithResponse(transferOptions, Context.NONE);
```

## Add/Remove Participants

### Add Participant

```java
CommunicationIdentifier participant = new PhoneNumberIdentifier("+16471234567");
PhoneNumberIdentifier callerId = new PhoneNumberIdentifier("+18001234567");

AddParticipantOptions addOptions = new AddParticipantOptions(
    new CallInvite(participant, callerId)
);

Response<AddParticipantResult> response = callAutomationClient
    .getCallConnection(callConnectionId)
    .addParticipantWithResponse(addOptions, Context.NONE);
```

### Remove Participant

```java
CommunicationIdentifier participant = new PhoneNumberIdentifier("+16471234567");

callAutomationClient.getCallConnection(callConnectionId)
    .removeParticipant(participant);
```

### List Participants

```java
ListParticipantsResult participants = callAutomationClient
    .getCallConnection(callConnectionId)
    .listParticipants();

participants.getValues().forEach(p -> 
    System.out.println("Participant: " + p.getIdentifier().getRawId()));
```

## Handling Call Events

### Event Grid Webhook Handler (Spring Boot)

```java
import com.azure.communication.callautomation.CallAutomationEventParser;
import com.azure.communication.callautomation.models.events.*;
import org.springframework.web.bind.annotation.*;

@RestController
public class CallbackController {

    @PostMapping("/Events")
    public ResponseEntity<String> handleCallEvents(@RequestBody String requestBody) {
        List<CallAutomationEventBase> events = CallAutomationEventParser.parseEvents(requestBody);
        
        for (CallAutomationEventBase event : events) {
            String callConnectionId = event.getCallConnectionId();
            
            if (event instanceof CallConnected) {
                handleCallConnected((CallConnected) event);
            } else if (event instanceof RecognizeCompleted) {
                handleRecognizeCompleted((RecognizeCompleted) event);
            } else if (event instanceof RecognizeFailed) {
                handleRecognizeFailed((RecognizeFailed) event);
            } else if (event instanceof PlayCompleted) {
                handlePlayCompleted((PlayCompleted) event);
            } else if (event instanceof CallDisconnected) {
                handleCallDisconnected((CallDisconnected) event);
            }
        }
        
        return ResponseEntity.ok("");
    }
    
    private void handleCallConnected(CallConnected event) {
        System.out.println("Call connected: " + event.getCallConnectionId());
    }
    
    private void handleRecognizeCompleted(RecognizeCompleted event) {
        CollectTonesResult result = (CollectTonesResult) event.getRecognizeResult();
        String tones = result.getTones().stream()
            .map(DtmfTone::toString)
            .collect(Collectors.joining());
        System.out.println("DTMF tones received: " + tones);
    }
    
    private void handleRecognizeFailed(RecognizeFailed event) {
        System.out.println("Recognition failed: " + event.getResultInformation().getMessage());
    }
    
    private void handlePlayCompleted(PlayCompleted event) {
        System.out.println("Play completed: " + event.getOperationContext());
    }
    
    private void handleCallDisconnected(CallDisconnected event) {
        System.out.println("Call disconnected: " + event.getCallConnectionId());
    }
}
```

## Async Client Patterns

### Create Call Async

```java
asyncClient.createCall(callInvite, callbackUri)
    .subscribe(
        result -> System.out.println("Call ID: " + 
            result.getCallConnectionProperties().getCallConnectionId()),
        error -> System.err.println("Error: " + error.getMessage())
    );
```

### Play Audio Async

```java
asyncClient.getCallConnectionAsync(callConnectionId)
    .getCallMediaAsync()
    .playToAll(textSource)
    .subscribe(
        unused -> System.out.println("Play started"),
        error -> System.err.println("Error: " + error.getMessage())
    );
```

### Recognize Async

```java
asyncClient.getCallConnectionAsync(callConnectionId)
    .getCallMediaAsync()
    .startRecognizingWithResponse(recognizeOptions)
    .subscribe(
        response -> System.out.println("Recognition started"),
        error -> System.err.println("Error: " + error.getMessage())
    );
```

## Error Handling

### Sync Error Handling

```java
import com.azure.core.exception.HttpResponseException;

try {
    Response<CreateCallResult> result = callAutomationClient.createCallWithResponse(
        createCallOptions, 
        Context.NONE
    );
} catch (HttpResponseException e) {
    System.err.println("HTTP Status: " + e.getResponse().getStatusCode());
    System.err.println("Error: " + e.getMessage());
} catch (Exception e) {
    System.err.println("Unexpected error: " + e.getMessage());
}
```

### Async Error Handling

```java
asyncClient.createCall(callInvite, callbackUri)
    .subscribe(
        result -> System.out.println("Success"),
        error -> {
            if (error instanceof HttpResponseException) {
                HttpResponseException httpError = (HttpResponseException) error;
                System.err.println("HTTP error: " + httpError.getResponse().getStatusCode());
            } else {
                System.err.println("Error: " + error.getMessage());
            }
        }
    );
```

### Common Error Codes

| Code | Meaning |
|------|---------|
| 400 | Invalid request |
| 401 | Authentication failed |
| 403 | Not authorized |
| 404 | Call/resource not found |
| 429 | Rate limited |
| 500 | Server error |

````
