Handling Incoming Phone Calls with Java and Twilio

April 23, 2021
Written by
Reviewed by

Title: Handling incoming phone calls with Java and Twilio

Making outbound phone calls with Java and Twilio is only half of the picture. To build a useful and engaging phone app you will need to handle what happens when people call you back.

To do that, you will need to configure a URL in your Twilio console. Incoming calls will trigger HTTP requests to that URL, and the response to those webhook requests will determine what happens next in the call. The HTTP responses contain an XML dialect called TwiML, which gives you a lot of flexibility in how the call is handled.

In this post I'll show how to set up a web server using Spring Boot to handle incoming calls with a spoken "hello world" and then show how to create a more interesting and interactive setup.

If you want to skip to the end, check out the completed project on GitHub.

Setting up

Before starting out you'll need:

The quickest way to start a brand new Spring Boot project is using the Spring Initializr. This link will take you to a preconfigured setup that has the web dependency selected and some names preconfigured. Load that page, click "Generate", and unzip the downloaded project into an empty directory. Then, open the project in your IDE of choice and let's get going.

Building the application

When you open the code in your IDE you will see the top level of the project. Inside the src/main/java folder there is a package called com.example.twilio.calls with a class called TwilioJavaHandlePhoneCallsApplication in it. You won't need to edit that class but it has a main method which will be helpful later on.

Creating an HTTP endpoint

Create another class in the same package. Call it PhoneCallHandler and give it the following content:

@RestController
public class PhoneCallHandler {

    @PostMapping("/")
    @ResponseBody
    public String handleIncomingCall(){
        return "Hello from your app 👋";
    }
}

[this code with imports on GitHub]

Run this application now, either by running the main method from TwilioJavaHandlePhoneCallsApplication in your IDE, or ./mvnw spring-boot:run in a terminal window at the root of your project.

After it's started up, test it using the command below.

curl -XPOST http://localhost:8080/

You should see "Hello from your app 👋" as the response.

Returning TwiML over HTTP

So far, so good. The app is handling HTTP requests and returning plain text. Next, modify it to return Twilio Markup Language (TwiML) code with a content-type of application/xml. TwiML can be written by hand, but it's easier and safer to create it using the Twilio Java Helper library. Add it as a dependency of your project by putting the following in the <dependencies> section of pom.xml in the root of your project:

<dependency>
         <groupId>com.twilio.sdk</groupId>
         <artifactId>twilio</artifactId>
         <version>8.10.0</version>
</dependency>

We always recommend using the latest version of the Twilio Helper Library. At the time of writing the latest version is 8.10.0 but new versions are released frequently. You can always check the latest version at mvnreporistory.com.

Next, we need to use TwiML to define what happens during the call. This is done in the PhoneCallHandler class. First, I'll give a short example using text-to-speech to say something. Then, I’ll provide a longer example that shows how you can build an interactive voice app.

<Say> something

Change the content of handleIncomingCall method in PhoneCallHandler to return TwiML with a content type of application/xml:

@PostMapping(value = "/", produces = "application/xml")
@ResponseBody
public String handleIncomingCall(){
    return new VoiceResponse.Builder()
        .say(
            new Say.Builder("Hello from Twilio").build()
        ).build().toXml();
}

[this code with imports on GitHub]

This method now returns the following TwiML, which I've formatted in order to make it more readable:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Hello from Twilio</Say>
</Response>

Connecting Twilio to your application

The content returned by handleIncomingCall is exactly what we need, but Twilio needs to be able to access it from the internet. There are lots of options for hosting Java apps online, but for a quick test like this I like to use a tool called ngrok which can create public URLs that forward to your localhost web apps.

Once you have installed ngrok, start your app using your IDE or the command from above. Then, run the following command to create a public URL for your local server:

ngrok http 8080

Once it has connected, ngrok displays the randomly generated URL that now points at your application. It should look like https://RANDOM_STRING.ngrok.io. Open up yourTwilio console to your incoming numbers and choose the number you want to use for this app, orbuy a new one. Edit the number and add your ngrok URL as the webhook for "when a call comes in" (leaving the method as POST).

If you have the Twilio CLI installed you can do this on the command line too, with the command below.

twilio phone-numbers:update PHONE_NUMBER --sms-url http://localhost:8080/messages

The CLI will detect that this is a localhost URL and set up ngrok for you.

Call your number, and listen to the voice saying "Hello from Twilio".  Magnificent work. Congratulations 🎉

An interactive example

Let’s step beyond the "hello world" of voice apps. To whet your appetite for some exciting possibilities, I'll show how to build something more fun and interactive. A caller will hear a short menu of options and can choose one by saying a number or typing it on their keypad.

Modify the handleIncomingCall method to use the <Gather> TwiML verb, which can do both speech and DTMF tone recognition:


private static final String MENU_OPTIONS = "Hello. Press or say One for a joke, or Two for some music.";
private static final String GOODBYE = "Thanks for calling, have a great day";

@PostMapping(value = "/", produces = "application/xml")
@ResponseBody
public String handleIncomingCall() {

    return new VoiceResponse.Builder()
        .gather(new Gather.Builder()
            .say(new Say.Builder(MENU_OPTIONS).build())
            .inputs(Arrays.asList(Gather.Input.DTMF, Gather.Input.SPEECH))
            .hints("one, two")
            .numDigits(1)
            .action("/gatherResult")
            .build())
        .say(new Say.Builder(GOODBYE).build())
        .build().toXml();
}

[this code with imports on GitHub]

The caller is prompted to say a number or press a button on their keypad using options which are all documented on the Gather TwiML docs. The most important of these is on line 14 which gives another URL to call when Twilio has recognised a word or a keypad press. If there is no input after the default 5 seconds we say the GOODBYE message and hang up.

/gatherResult is a relative URL so another method is needed in PhoneCallHandler to determine what happens after the caller has made a choice:

private static final String JOKE = "How do you know if a bee is using your phone? The line will be buzzy.";
private static final String MUSIC_URL = "http://demo.twilio.com/docs/classic.mp3";

@PostMapping(value = "/gatherResult", produces = "application/xml")
@ResponseBody
public String handleGatherResult(
    @RequestParam(value = "Digits", required = false) String digits,
    @RequestParam(value = "SpeechResult", required = false) String speechResult) {

    if ("1".equals(digits) || "one".equals(speechResult)) {
        return new VoiceResponse.Builder()
            .say(new Say.Builder(JOKE).build())
            .build().toXml();
    }

    if ("2".equals(digits) || "two".equals(speechResult)) {
        return new VoiceResponse.Builder()
            .play(new Play.Builder(MUSIC_URL).build())
            .build().toXml();
    }

    // if they said or typed something we didn't expect, read the choices again.
    return handleIncomingCall();
}

[this code including imports on GitHub]

Depending on how the caller made their choice, either digits or speechResult will be included in the HTTP request which Twilio sends to find out what to do next. If they chose a valid option we can generate TwiML to tell a joke or play some music by using the <Say> and <Play> verbs. The call ends after either of those verbs run. If the caller did something unexpected, the code calls handleIncomingCall to generate the TwiML that reads the choices to the caller again.

Restart the application and make another call to hear it in action. Fun, isn't it?

Wrapping up

You've learned how to respond to incoming voice calls using Twilio and Java and you've seen that you can chain calls together to build interactive voice apps. Now the limit is your imagination. Whatever you're building, I'd love to hear about it. Please get in touch:

I can't wait to see what you build!