Call Tracking with Java and Servlets

January 10, 2017
Written by
Jose Oliveros
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Jose Oliveros
Contributor
Opinions expressed by Twilio contributors are their own
Kat King
Twilion
Paul Kamp
Twilion

call-tracking-java-servlets

This Java Servlets web application shows how you can use Twilio to track the effectiveness of different marketing channels.

This application has three main features:

  • It purchases phone numbers from Twilio to use in different marketing campaigns (like a billboard or a bus advertisement)
  • It forwards incoming calls for those phone numbers to a salesperson
  • It displays charts showing data about the phone numbers and the calls they receive

In this tutorial, we'll point out the key bits of code that make this application work. Check out the project README on GitHub to see how to run the code yourself.

Search for Available Phone Numbers

Call tracking requires us to search for and buy phone numbers on demand, associating a specific phone number with a lead source. This class uses the Twilio Java Helper Library to search for phone numbers by area code and return a list of numbers that are available for purchase.

Editor: this is a migrated tutorial. Find the original code at https://github.com/TwilioDevEd/call-tracking-servlets/

package com.twilio.calltracking.lib.services;

import com.twilio.Twilio;
import com.twilio.base.ResourceSet;
import com.twilio.calltracking.lib.Config;
import com.twilio.rest.api.v2010.account.Application;
import com.twilio.rest.api.v2010.account.availablephonenumbercountry.Local;
import com.twilio.rest.api.v2010.account.availablephonenumbercountry.LocalReader;
import com.twilio.rest.api.v2010.account.incomingphonenumber.LocalCreator;
import com.twilio.type.PhoneNumber;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@SuppressWarnings("unused")
public class TwilioServices {

    private static final String DEFAULT_APP_NAME = "Call Tracking App";

    public TwilioServices() {
        Twilio.init(Config.getAccountSid(), Config.getAuthToken());
    }

    public List<Local> searchPhoneNumbers(String areaCode) {

        LocalReader localReader = Local.reader("US");
        if (areaCode != null) {
            localReader.setAreaCode(Integer.parseInt(areaCode));
        }

        Iterator<Local> phoneNumbers = localReader.read().iterator();

        List<Local> phoneNumbersAsList = new ArrayList<>();
        phoneNumbers.forEachRemaining(phoneNumbersAsList::add);

        return phoneNumbersAsList;
    }

    public com.twilio.rest.api.v2010.account.incomingphonenumber.Local purchasePhoneNumber(
            String phoneNumber, String applicationSid) {

        return new LocalCreator(new PhoneNumber(phoneNumber))
                .setVoiceApplicationSid(applicationSid)
                .create();
    }

    public String getApplicationSid() {
        ResourceSet<Application> apps = getApplications();

        Application app = apps.iterator().hasNext()
                ? apps.iterator().next()
                : Application.creator(DEFAULT_APP_NAME).create();

        return app.getSid();
    }

    private ResourceSet<Application> getApplications() {
        return Application
                .reader()
                .setFriendlyName(DEFAULT_APP_NAME)
                .read();
    }
}

Now let's see how we will display these numbers for the user to purchase them and enable their campaigns.

Display Available Phone Numbers

We display a form to the user on the app's home page which allows them to search for a new phone number by area code. At the controller level we use the TwilioServices instances we created earlier to actually search for numbers. This will render the view that contains a list of numbers they can choose to buy.

package com.twilio.calltracking.servlets.phonenumbers;

import com.twilio.calltracking.lib.services.TwilioServices;
import com.twilio.calltracking.servlets.WebAppServlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.stream.Collectors;

public class AvailableServlet extends WebAppServlet {

    private TwilioServices twilioServices;

    @SuppressWarnings("unused")
    public AvailableServlet() {
        this(new TwilioServices());
    }

    public AvailableServlet(TwilioServices twilioServices) {
        this.twilioServices = twilioServices;
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        String areaCode = request.getParameter("areaCode");

        request.setAttribute("phoneNumbers",
                twilioServices.searchPhoneNumbers(areaCode)
                        .stream()
                        .limit(10)
                        .collect(Collectors.toList()));

        request.getRequestDispatcher("/available_phone_numbers.jsp").forward(request, response);
    }
}

We've seen how we can display available phone numbers for purchase with the help of the Twilio C# helper library. Now let's look at how we can buy an available phone number.

Buy a Phone Number

Our PurchasePhoneNumber method takes two parameters, the first one is a phone number and the second one is the application SID. Now our Twilio API client can purchase the available phone number our user chooses.

package com.twilio.calltracking.lib.services;

import com.twilio.Twilio;
import com.twilio.base.ResourceSet;
import com.twilio.calltracking.lib.Config;
import com.twilio.rest.api.v2010.account.Application;
import com.twilio.rest.api.v2010.account.availablephonenumbercountry.Local;
import com.twilio.rest.api.v2010.account.availablephonenumbercountry.LocalReader;
import com.twilio.rest.api.v2010.account.incomingphonenumber.LocalCreator;
import com.twilio.type.PhoneNumber;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@SuppressWarnings("unused")
public class TwilioServices {

    private static final String DEFAULT_APP_NAME = "Call Tracking App";

    public TwilioServices() {
        Twilio.init(Config.getAccountSid(), Config.getAuthToken());
    }

    public List<Local> searchPhoneNumbers(String areaCode) {

        LocalReader localReader = Local.reader("US");
        if (areaCode != null) {
            localReader.setAreaCode(Integer.parseInt(areaCode));
        }

        Iterator<Local> phoneNumbers = localReader.read().iterator();

        List<Local> phoneNumbersAsList = new ArrayList<>();
        phoneNumbers.forEachRemaining(phoneNumbersAsList::add);

        return phoneNumbersAsList;
    }

    public com.twilio.rest.api.v2010.account.incomingphonenumber.Local purchasePhoneNumber(
            String phoneNumber, String applicationSid) {

        return new LocalCreator(new PhoneNumber(phoneNumber))
                .setVoiceApplicationSid(applicationSid)
                .create();
    }

    public String getApplicationSid() {
        ResourceSet<Application> apps = getApplications();

        Application app = apps.iterator().hasNext()
                ? apps.iterator().next()
                : Application.creator(DEFAULT_APP_NAME).create();

        return app.getSid();
    }

    private ResourceSet<Application> getApplications() {
        return Application
                .reader()
                .setFriendlyName(DEFAULT_APP_NAME)
                .read();
    }
}

If you don't know where you can get this application SID, don't panic, the next step will show you how.

Set webhook URLs in a TwiML Application

When we purchase a phone number, we specify a voice application SID. This is an identifier for a TwiML application, which you can create through the REST API or your Twilio Console.

Create TwiML App

Associate a Phone Number with a Lead Source

Once we search for and buy a Twilio number, we need to associate it with a lead source in our database. This is the core of a call tracking application. Any phone calls to our new Twilio number will be attributed to this source.

package com.twilio.calltracking.servlets.leadsources;

import com.twilio.calltracking.lib.Config;
import com.twilio.calltracking.lib.services.TwilioServices;
import com.twilio.calltracking.models.LeadSource;
import com.twilio.calltracking.repositories.LeadSourceRepository;
import com.twilio.calltracking.servlets.WebAppServlet;
import com.twilio.rest.api.v2010.account.incomingphonenumber.Local;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

public class CreateServlet extends WebAppServlet {

    private LeadSourceRepository leadSourceRepository;
    private TwilioServices twilioServices;

    @SuppressWarnings("unused")
    public CreateServlet() {
        this(new LeadSourceRepository(), new TwilioServices());
    }

    public CreateServlet(LeadSourceRepository leadSourceRepository, TwilioServices twilioServices) {
        this.leadSourceRepository = leadSourceRepository;
        this.twilioServices = twilioServices;
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String phoneNumber = request.getParameter("phoneNumber");

        String twimlApplicationSid = Config.getTwimlApplicationSid();
        if (Objects.equals(twimlApplicationSid, "") || (twimlApplicationSid == null)) {
            twimlApplicationSid = twilioServices.getApplicationSid();
        }

        Local twilioNumber = twilioServices.purchasePhoneNumber(phoneNumber, twimlApplicationSid);

        LeadSource leadSource = leadSourceRepository.create(new LeadSource(
                twilioNumber.getFriendlyName(),
                twilioNumber.getPhoneNumber().toString()));

        response.sendRedirect(String.format("/leadsources/edit?id=%s", leadSource.getId()));
    }
}

So far our method for creating a Lead Source and associating a Twilio phone number with it is pretty straightforward. Now let's have a closer look at our Lead Source model which will store this information.

The LeadSource Model

The LeadSource model associates a Twilio number to a named lead source (like "Wall Street Journal Ad" or "Dancing guy with sign"). It also tracks a phone number to which we'd like all the calls redirected, like your sales or support help line.

package com.twilio.calltracking.models;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GenerationType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;

@SuppressWarnings("unused")
@Entity
@Table(name = "lead_sources")
public class LeadSource {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private long id;

    @Column(name = "name")
    private String name;

    @Column(name = "incoming_number_national")
    private String incomingNumberNational;

    @Column(name = "incoming_number_international")
    private String incomingNumberInternational;

    @Column(name = "forwarding_number")
    private String forwardingNumber;

    @OneToMany(mappedBy = "leadSource")
    private List<Lead> leads;

    public LeadSource() {
        this.leads = new ArrayList<>();
    }

    public LeadSource(
            String name, String incomingNumberNational, String incomingNumberInternational) {
        this(incomingNumberNational, incomingNumberInternational);
        this.name = name;
    }

    public LeadSource(String incomingNumberNational, String incomingNumberInternational) {
        this();
        this.incomingNumberNational = incomingNumberNational;
        this.incomingNumberInternational = incomingNumberInternational;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIncomingNumberNational() {
        return incomingNumberNational;
    }

    public void setIncomingNumberNational(String incomingNumberNational) {
        this.incomingNumberNational = incomingNumberNational;
    }

    public String getIncomingNumberInternational() {
        return incomingNumberInternational;
    }

    public void setIncomingNumberInternational(String incomingNumberInternational) {
        this.incomingNumberInternational = incomingNumberInternational;
    }

    public String getForwardingNumber() {
        return forwardingNumber;
    }

    public void setForwardingNumber(String forwardingNumber) {
        this.forwardingNumber = forwardingNumber;
    }
}

As the application will be collecting leads and associating them to each LeadSource or campaign, it is necessary to have a Lead model as well to keep track of each Lead as it comes in and associate it to the LeadSource.

The Lead Model

A Lead represents a phone call generated by a LeadSource. Each time somebody calls a phone number associated with a LeadSource, we'll use the Lead model to record some of the data Twilio gives us about their call.

package com.twilio.calltracking.models;


import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GenerationType;
import javax.persistence.GeneratedValue;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Id;
import javax.persistence.Table;

@SuppressWarnings("unused")
@Entity
@Table(name = "leads")
public class Lead {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private long id;

    @Column(name = "phone_number")
    private String phoneNumber;

    @Column(name = "city")
    private String city;

    @Column(name = "state")
    private String state;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "lead_source_id")
    private LeadSource leadSource;

    public Lead() {
    }

    public Lead(String phoneNumber, String city, String state, LeadSource leadSource) {
        this.phoneNumber = phoneNumber;
        this.city = city;
        this.state = state;
        this.leadSource = leadSource;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public LeadSource getLeadSource() {
        return leadSource;
    }

    public void setLeadSource(LeadSource leadSource) {
        this.leadSource = leadSource;
    }
}

The backend part of the code which creates a LeadSource as well as a Twilio Number is complete. The next part of the application will be the webhooks that will handle incoming calls and forward them to the appropriate sales team member. Let's us see the way these webhooks are built.

Forward Calls and Create Leads

Whenever a customer calls one of our Twilio numbers, Twilio will send a POST request to the URL associated with this action (should be /lead).

We use the incoming call data to create a new Lead for a LeadSource, then return TwiML that connects our caller with the forwardingNumber of our LeadSource.

package com.twilio.calltracking.servlets.calltracking;

import com.twilio.calltracking.models.Lead;
import com.twilio.calltracking.models.LeadSource;
import com.twilio.calltracking.repositories.LeadRepository;
import com.twilio.calltracking.repositories.LeadSourceRepository;
import com.twilio.calltracking.servlets.WebAppServlet;
import com.twilio.twiml.voice.Dial;
import com.twilio.twiml.voice.Number;
import com.twilio.twiml.VoiceResponse;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LeadCallServlet extends WebAppServlet {

    private LeadSourceRepository leadSourceRepository;
    private LeadRepository leadRepository;

    @SuppressWarnings("unused")
    public LeadCallServlet() {
        this(new LeadSourceRepository(), new LeadRepository());
    }

    public LeadCallServlet(LeadSourceRepository leadSourceRepository,
        LeadRepository leadRepository) {
        this.leadSourceRepository = leadSourceRepository;
        this.leadRepository = leadRepository;
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String called = request.getParameter("Called");
        String caller = request.getParameter("Caller");
        String city = request.getParameter("FromCity");
        String state = request.getParameter("FromState");

        LeadSource ls = leadSourceRepository.findByIncomingNumberInternational(called);
        leadRepository.create(new Lead(caller,  city, state, ls));

        Number number = new Number.Builder(ls.getForwardingNumber()).build();
        VoiceResponse voiceResponse = new VoiceResponse.Builder()
                .dial(new Dial.Builder().number(number).build())
                .build();

        respondTwiML(response, voiceResponse);
    }
}

Once we have forwarded calls and created leads, we will have a lot of incoming calls that will create leads, and that will be data for us but we need to transform that data into information in order to get benefits from it. So, let's see how we get statistics from these sources on the next step.

Get Statistics about Our Lead Sources

One useful statistic we can get from our data is how many calls each LeadSource has received. We query over LeadSource and count its Lead models.

package com.twilio.calltracking.repositories;

import com.twilio.calltracking.models.LeadSource;

import javax.persistence.NoResultException;
import java.util.List;

public class LeadSourceRepository extends Repository<LeadSource> {

    public LeadSourceRepository() {
        super(LeadSource.class);
    }

    public LeadSource findByIncomingNumberInternational(String number) {

        LeadSource leadSource = null;
        try {
            leadSource = (LeadSource) getEm().createQuery(
                "SELECT e FROM LeadSource e WHERE e.incomingNumberInternational = :number")
                .setMaxResults(1).setParameter("number", number).getSingleResult();
        } catch (NoResultException ex) {
            System.out.println(ex.getMessage());
        }

        return leadSource;
    }

    public List<Object> findLeadsByLeadSource() {

        List items = null;
        try {
            items = getEm().createQuery(
                "SELECT s.name, COUNT(l) FROM LeadSource s JOIN s.leads l GROUP BY s.name")
                .getResultList();
        } catch (NoResultException ex) {
            System.out.println(ex.getMessage());
        }

        return QueryHelper.mapResults(items);
    }

    public List<Object> findLeadsByCity() {

        List items = null;
        try {
            items = getEm().createQuery(
                "SELECT l.city, COUNT(l) FROM LeadSource s JOIN s.leads l GROUP BY l.city")
                .getResultList();
        } catch (NoResultException ex) {
            System.out.println(ex.getMessage());
        }

        return QueryHelper.mapResults(items);
    }
}

Up until this point, we have been focusing on the backend code to our application. Which is ready to start handling incoming calls or leads. Next, let's turn our attention to the client side. Which, in this case, is a simple Javascript application, along with Chart.js which will render these stats in an appropriate way.

Visualize our statistics with Chart.js

Back on the home page, we fetch call tracking statistics in JSON from the server using Jackson/ObjectMapper and jQuery. We display the stats in colorful pie charts we create with Chart.js.

$(document).ready(function () {
    $.get("/stats/source", function (data) {
        CallTrackingGraph("#leads-by-source", data).draw();
        console.log(data);
    });

    $.get("/stats/city", function (data) {
        CallTrackingGraph("#leads-by-city", data).draw();
        console.log(data);
    });
});

CallTrackingGraph = function (selector, data) {
    function getContext() {
        return $(selector).get(0).getContext("2d");
    }

    return {
        draw: function () {
            var context = getContext(selector);
            new Chart(context).Pie(data);
        }
    }
}

That's it! Our Java Servlet application is now ready to purchase new phone numbers, forward incoming calls, and record some statistics for our business.

Where to Next?

If you're a Java developer working with Twilio, you might enjoy these other tutorials:

SMS and MMS notifications

Build a server notification system that will alert all administrators via SMS when a server outage occurs.

Click To Call

Convert web traffic into phone calls with the click of a button.

Did this help?

Thanks for checking this tutorial out! If you have any feedback to share with us please contact us on Twitter, we'd love to hear it.