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:
- An installation of Java 8 or newer - I recommend SDKMAN! for installing and managing Java versions.
- A Java IDE - I'm a fan of IntelliJ IDEA, but if you have a favourite that's cool too.
- A Twilio account (if you don't have one yet, sign up for a free account here and receive a $10 credit when you upgrade)
- A Twilio phone number that can make and receive calls
- Either ngrok or the Twilio CLI, so that you canrespond to webhooks in your local development environment
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!