Receiving and Responding to SMS with Java and Twilio

March 23, 2021
Written by
Reviewed by

Title: Receiving and Responding to SMS with Java and Twilio

If you're wondering how to send SMS from Java code, we've got you covered already. However, people are increasingly expecting to be able to converse with companies and services rather than just get a never-ending stream of notifications. To build really engaging and interactive apps you're going to want to respond to incoming messages too.

To do that, you will need to configure a URL in your Twilio console. Incoming messages will trigger HTTP requests to that URL, and the response to those webhook requests will determine what happens next, including sending responses. Responses should be written in an XML dialect called Twilio Markup Language, or TwiML.

In this post I'll walk you through setting up a web server using Spring Boot to do just that.

Getting set up

Before we get started, you'll need:

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

Building the application

When you open up the codebase you will see the top level of the project. Open the nested folders src/main/java and find the package called com.example.twilio.sms, with a single class, called RespondToSmsWithTwilioAndJavaApplication. You won't need to edit that class, but it has a main method that will be helpful later.

Creating an HTTP endpoint

For now, create another class in the same package with the following definition, and call it SmsWebhookHandler:

@RestController
public class SmsWebhookHandler {
    @PostMapping("/")
    @ResponseBody
    public String handleSmsWebhook(){
      return "Hello from Twilio";
    }
}

[full code including imports on GitHub]

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

After it's running, test it by using the command below.

curl -XPOST http://localhost:8080

You should see "Hello from Twilio" as the response.

Returning TwiML over HTTP

We've made a good start so far. Next we need to return TwiML so that Twilio can understand the response.

It's possible to write TwiML by hand, but easier to do using the Twilio Java Helper Library. Add the code below to the <dependencies> section of pom.xml in the root of your project.

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

We always recommend using the latest version of the Twilio Helper Library. At the time of writing this is version `8.8.0`. You can always check the latest version at mvnreporistory.com.

Now, let's make a few changes to the code in SmsWebhookHandler. Change the class to have this definition:

@RestController
public class SmsWebhookHandler {

    private final Map<String, Integer> messageCounts = new ConcurrentHashMap<>();

    @PostMapping(value = "/", produces = "application/xml")
    @ResponseBody
    public String handleSmsWebhook(
        @RequestParam("From") String from,
        @RequestParam("Body") String body){

        int thisMessageCount = messageCounts.compute(from, (k,v) -> (v == null) ? 1 : v+1);

        String plural = (thisMessageCount > 1) ? "messages" : "message";
        String message = String.format(
            "☎️ Hello from Twilio. You've sent %d %s, and this one said '%s'",
            thisMessageCount, plural, body);

        return new MessagingResponse.Builder()
            .message(new Message.Builder(message).build())
            .build().toXml();
    }
}

[full code including imports on GitHub]

There are a few things going on here, so let's step through them:

  • Line 4: creates a new map from String to Integer to count how many messages we've had from each unique phone number. Because Spring will only create one instance of this class, the Map is shared between all calls.
  • Line 6: notice that the endpoint now has a content-type of application/xml.
  • Lines 9-10: we can pull out data from the incoming request using @RequestParam. Here I'm only using the From number and the message Body but there is a lot more you could access.
  • Line 12: update the messageCounts map to account for this new message.
  • Lines 14-17: build the String to be used as the message response.
  • Lines 19-21: finally, build the TwiML response and return it as an XML String.

To test the code, run the project as before. Then, when it's running, run the command below.

curl http://localhost:8080/ -F From=+1234567890 -F Body=Hello

You should receive the following response:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Message>☎️ Hello from Twilio. You've sent 1 message, and this one said 'Hello'</Message>
</Response>

(note that I've added line-breaks to this output)

Connecting to a Twilio phone number

The last piece of the puzzle is how to configure Twilio to use this app when a message comes in. As I said in the introduction, you need to configure your phone number with a URL for the application. Because you're running the application locally, you only have a localhost URL, which Twilio won't be able to access. There are several options for public hosting with Java, but one tool which I like to use for development is ngrok, which can create public URLs that forward to your localhost web apps.

Once you have installed ngrok and restarted your app using your IDE or the command from above, the following command will create a public URL for your local server:

ngrok http 8080

Once the tunnel 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 your Twilio console to your incoming numbers and choose the number you want to use for this app, or buy a new one. Edit the number and add your ngrok URL as the webhook for when a message 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:3000/messages

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

Text your number a few times and see the replies roll in.

Screenshot of the SMS app on my phone. I&#x27;ve sent 2 messages and had 2 replies which are echo what I sent in and are numbered.

🎉🎉🎉 Nice Work 🎉🎉🎉

Wrapping up

You've learned how to respond to SMS using Twilio and Java, and you can learn how to initiate outbound SMS. Also, you've seen that you can customize responses based on who is messaging you and what they say. Now the limit is your imagination. Will you build a book(store) recommender, a community healthcare system, a chatbot, or something brand new?

Whatever you're building, I'd love to hear about it - get in touch

I can't wait to see what you build!