ETA Notifications with Java and Servlets

ETA Notifications

Companies like Uber, TaskRabbit, and Instacart have built an entire industry around the fact that we, the customers, like to order things instantly wherever we are. The key to those services working? Notifying customers when things change.

Uber relies on Twilio SMS to keep customers up to date on their ridesharing request. Learn more here.

In this tutorial, we'll build a notification system for a fake on-demand laundry service Laundr.io using Java and Servlets.

Let's get started!  Click the below button to begin.

Trigger a Customer Notification

A driver's screen shows two buttons that allow the laundry delivery person to trigger notifications: one for picking up orders and one for delivering them.

This means we'll have two cases to handle:

  1. Delivery person picks up laundry to be delivered ( /pickup )
  2. Delivery person is arriving at the customer's house ( /deliver )

In a production app we would probably trigger the second notification when the delivery person was physically near the customer, using GPS.

(In this case we'll just use a button.)

Loading Code Samples...
Language
package com.twilio.etanotifications.servlets;

import com.twilio.etanotifications.lib.MessageSender;
import com.twilio.etanotifications.models.Order;
import com.twilio.etanotifications.repositories.OrdersRepository;

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

public class OrderDeliverServlet extends HttpServlet {
  private final OrdersRepository repository;
  private final MessageSender messageSender;

  @SuppressWarnings("unused")
  public OrderDeliverServlet() {
    this(new OrdersRepository(), MessageSender.getMessageSender());
  }

  public OrderDeliverServlet(OrdersRepository repository, MessageSender messageSender) {
    this.repository = repository;
    this.messageSender = messageSender;
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    String pickupMessage = "Your laundry is arriving now.";
    String orderId = request.getParameter("id");
    int id = Integer.parseInt(orderId);

    Order order = repository.find(id);
    order.setStatus("Delivered");
    order.setNotificationStatus("queued");
    order = repository.update(order);

    String callbackUrl = request.getRequestURL().toString().replace(request.getRequestURI(), "") +
        "/notification/status/update?id=" + order.getId();

    messageSender.sendSMS(order.getCustomerPhoneNumber(), pickupMessage, callbackUrl);

    response.sendRedirect("/orders");
  }
}
src/main/java/com/twilio/etanotifications/servlets/OrderDeliverServlet.java
Trigger a notification

src/main/java/com/twilio/etanotifications/servlets/OrderDeliverServlet.java

Let's look at how to use the Twilio REST API Client to send out a notification.

Set up the Twilio REST Client

Here we create a helper class with an authenticated Twilio REST API client that we can use anytime we need to send a text message.

We initialize it with our Twilio Account Credentials stored as environment variables.  You can find the Auth Token and Account SID in the console:

console credentials

Loading Code Samples...
Language
package com.twilio.etanotifications.lib;

import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.MessageCreator;
import com.twilio.type.PhoneNumber;

public class MessageSender {
  private static MessageSender instance = new MessageSender();
  private TwilioRestClient client;
  private AppSetup appSetup;

  public MessageSender() {
    this.appSetup = new AppSetup();
    this.client = new TwilioRestClient.Builder(appSetup.getAccountSid(), appSetup.getAuthToken()).build();
  }

  public MessageSender(TwilioRestClient client, AppSetup appSetup) {
    this.client = client;
    this.appSetup = appSetup;
  }

  public static MessageSender getMessageSender() {
    return instance;
  }

  public int sendSMS(String toNumber, String messageBody, String callbackUrl) {
    try {
      new MessageCreator(
              new PhoneNumber(toNumber),
              new PhoneNumber(appSetup.getTwilioNumber()),
              messageBody
      ).setStatusCallback(callbackUrl).create(client);
    } catch (Exception e) {
      e.printStackTrace();
      return 1;
    }
    return 0;
  }
}
src/main/java/com/twilio/etanotifications/lib/MessageSender.java
Set up the Twilio REST Client

src/main/java/com/twilio/etanotifications/lib/MessageSender.java

Next up, we'll look at how we handle a notification request.

Handle the Notification Trigger

This code handles the HTTP POST request triggered by the delivery person.

It uses our MessageSender class to send an SMS message to the customer's phone number, which we have registered in our database.  Easy!

Loading Code Samples...
Language
package com.twilio.etanotifications.servlets;

import com.twilio.etanotifications.lib.MessageSender;
import com.twilio.etanotifications.models.Order;
import com.twilio.etanotifications.repositories.OrdersRepository;

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

public class OrderPickupServlet extends HttpServlet {
  private final OrdersRepository repository;
  private final MessageSender messageSender;

  @SuppressWarnings("unused")
  public OrderPickupServlet() {
    this(new OrdersRepository(), MessageSender.getMessageSender());
  }

  public OrderPickupServlet(OrdersRepository repository, MessageSender messageSender) {
    this.repository = repository;
    this.messageSender = messageSender;
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    String pickupMessage = "Your laundry is done and on its way to you!";
    String orderId = request.getParameter("id");
    int id = Integer.parseInt(orderId);

    Order order = repository.find(id);
    order.setStatus("Shipped");
    order.setNotificationStatus("queued");
    order = repository.update(order);

    String callbackUrl = request.getRequestURL().toString().replace(request.getRequestURI(), "") +
        "/notification/status/update?id=" + order.getId();

    int success = 0;
    try {
      success = messageSender.sendSMS(order.getCustomerPhoneNumber(), pickupMessage, callbackUrl);
    } catch (Exception e) {
      throw new ServletException(e.getLocalizedMessage());
    }

    if (success == 0) {
      response.sendRedirect(String.format("/order?id=%d", id));
    } else {
      throw new ServletException(
          String.format("An error occurred while sending the SMS. Error code: %d", success));
    }
  }
}
src/main/java/com/twilio/etanotifications/servlets/OrderPickupServlet.java
Handle a notification trigger

src/main/java/com/twilio/etanotifications/servlets/OrderPickupServlet.java

Next let's look closer at how we send the SMS.

Sending the Message

Here we demonstrate actually sending an SMS or MMS.

Think it needs a picture of the clothes?  Good idea.

You can pass along an optional media URL:

.setMediaUrl("http://lorempixel.com/image_output/fashion-q-c-640-480-1.jpg")

In addition to the required parameters (and optional media), we can pass a StatusCallback url to let us know if the message was delivered.

Loading Code Samples...
Language
package com.twilio.etanotifications.lib;

import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.MessageCreator;
import com.twilio.type.PhoneNumber;

public class MessageSender {
  private static MessageSender instance = new MessageSender();
  private TwilioRestClient client;
  private AppSetup appSetup;

  public MessageSender() {
    this.appSetup = new AppSetup();
    this.client = new TwilioRestClient.Builder(appSetup.getAccountSid(), appSetup.getAuthToken()).build();
  }

  public MessageSender(TwilioRestClient client, AppSetup appSetup) {
    this.client = client;
    this.appSetup = appSetup;
  }

  public static MessageSender getMessageSender() {
    return instance;
  }

  public int sendSMS(String toNumber, String messageBody, String callbackUrl) {
    try {
      new MessageCreator(
              new PhoneNumber(toNumber),
              new PhoneNumber(appSetup.getTwilioNumber()),
              messageBody
      ).setStatusCallback(callbackUrl).create(client);
    } catch (Exception e) {
      e.printStackTrace();
      return 1;
    }
    return 0;
  }
}
src/main/java/com/twilio/etanotifications/lib/MessageSender.java
Use Twilio Java Client and send an SMS

src/main/java/com/twilio/etanotifications/lib/MessageSender.java

Message status updates are interesting - let's take a closer look.

Handle a Twilio Message Status Callback

Twilio will make a POST request to this servlet each time our message status changes to one of the following: queued, failed, sent, delivered, or undelivered.

We then update this notificationStatus on the Order and let the business logic take over. This is a great place to add logic that would resend the message if it failed or send out an automated survey a few minutes after the customer has clothes delivered.

Loading Code Samples...
Language
package com.twilio.etanotifications.servlets;

import com.twilio.etanotifications.models.Order;
import com.twilio.etanotifications.repositories.OrdersRepository;

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

public class NotificationStatusServlet extends HttpServlet {
  private final OrdersRepository repository;

  @SuppressWarnings("unused")
  public NotificationStatusServlet() {
    this(new OrdersRepository());
  }

  public NotificationStatusServlet(OrdersRepository repository) {
    this.repository = repository;
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    String orderId = request.getParameter("id");
    String status = request.getParameter("MessageStatus");
    int id = Integer.parseInt(orderId);

    Order order = repository.find(id);
    order.setNotificationStatus(status);
    repository.update(order);
  }
}
src/main/java/com/twilio/etanotifications/servlets/NotificationStatusServlet.java
Update Order notification_status using a Twilio Callback

src/main/java/com/twilio/etanotifications/servlets/NotificationStatusServlet.java

That's a wrap - and a fold! We've just implemented an on-demand notification service that alerts our customers when their order is picked up or arriving.

Next, let's look at some other easy to integrate features.

Where to next?

Java and Twilio go well together!  Here are some other Java Servlets tutorials:

Workflow Automation

Increase your rate of response by automating the workflows that are key to your business. In this tutorial, learn how to build a ready-for-scale automated SMS workflow for a vacation rental company.

Masked Numbers

Protect your users' privacy by anonymously connecting them with Twilio Voice and SMS. Learn how to create disposable phone numbers on-demand so two users can communicate without exchanging personal information.

Did this help?

Thanks for checking this tutorial out! Let us know what you've built - or what you're building - on Twitter.

Mario Celi
Paul Kamp
Andrew Baker
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
package com.twilio.etanotifications.servlets;

import com.twilio.etanotifications.lib.MessageSender;
import com.twilio.etanotifications.models.Order;
import com.twilio.etanotifications.repositories.OrdersRepository;

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

public class OrderDeliverServlet extends HttpServlet {
  private final OrdersRepository repository;
  private final MessageSender messageSender;

  @SuppressWarnings("unused")
  public OrderDeliverServlet() {
    this(new OrdersRepository(), MessageSender.getMessageSender());
  }

  public OrderDeliverServlet(OrdersRepository repository, MessageSender messageSender) {
    this.repository = repository;
    this.messageSender = messageSender;
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    String pickupMessage = "Your laundry is arriving now.";
    String orderId = request.getParameter("id");
    int id = Integer.parseInt(orderId);

    Order order = repository.find(id);
    order.setStatus("Delivered");
    order.setNotificationStatus("queued");
    order = repository.update(order);

    String callbackUrl = request.getRequestURL().toString().replace(request.getRequestURI(), "") +
        "/notification/status/update?id=" + order.getId();

    messageSender.sendSMS(order.getCustomerPhoneNumber(), pickupMessage, callbackUrl);

    response.sendRedirect("/orders");
  }
}
package com.twilio.etanotifications.lib;

import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.MessageCreator;
import com.twilio.type.PhoneNumber;

public class MessageSender {
  private static MessageSender instance = new MessageSender();
  private TwilioRestClient client;
  private AppSetup appSetup;

  public MessageSender() {
    this.appSetup = new AppSetup();
    this.client = new TwilioRestClient.Builder(appSetup.getAccountSid(), appSetup.getAuthToken()).build();
  }

  public MessageSender(TwilioRestClient client, AppSetup appSetup) {
    this.client = client;
    this.appSetup = appSetup;
  }

  public static MessageSender getMessageSender() {
    return instance;
  }

  public int sendSMS(String toNumber, String messageBody, String callbackUrl) {
    try {
      new MessageCreator(
              new PhoneNumber(toNumber),
              new PhoneNumber(appSetup.getTwilioNumber()),
              messageBody
      ).setStatusCallback(callbackUrl).create(client);
    } catch (Exception e) {
      e.printStackTrace();
      return 1;
    }
    return 0;
  }
}
package com.twilio.etanotifications.servlets;

import com.twilio.etanotifications.lib.MessageSender;
import com.twilio.etanotifications.models.Order;
import com.twilio.etanotifications.repositories.OrdersRepository;

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

public class OrderPickupServlet extends HttpServlet {
  private final OrdersRepository repository;
  private final MessageSender messageSender;

  @SuppressWarnings("unused")
  public OrderPickupServlet() {
    this(new OrdersRepository(), MessageSender.getMessageSender());
  }

  public OrderPickupServlet(OrdersRepository repository, MessageSender messageSender) {
    this.repository = repository;
    this.messageSender = messageSender;
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    String pickupMessage = "Your laundry is done and on its way to you!";
    String orderId = request.getParameter("id");
    int id = Integer.parseInt(orderId);

    Order order = repository.find(id);
    order.setStatus("Shipped");
    order.setNotificationStatus("queued");
    order = repository.update(order);

    String callbackUrl = request.getRequestURL().toString().replace(request.getRequestURI(), "") +
        "/notification/status/update?id=" + order.getId();

    int success = 0;
    try {
      success = messageSender.sendSMS(order.getCustomerPhoneNumber(), pickupMessage, callbackUrl);
    } catch (Exception e) {
      throw new ServletException(e.getLocalizedMessage());
    }

    if (success == 0) {
      response.sendRedirect(String.format("/order?id=%d", id));
    } else {
      throw new ServletException(
          String.format("An error occurred while sending the SMS. Error code: %d", success));
    }
  }
}
package com.twilio.etanotifications.lib;

import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.MessageCreator;
import com.twilio.type.PhoneNumber;

public class MessageSender {
  private static MessageSender instance = new MessageSender();
  private TwilioRestClient client;
  private AppSetup appSetup;

  public MessageSender() {
    this.appSetup = new AppSetup();
    this.client = new TwilioRestClient.Builder(appSetup.getAccountSid(), appSetup.getAuthToken()).build();
  }

  public MessageSender(TwilioRestClient client, AppSetup appSetup) {
    this.client = client;
    this.appSetup = appSetup;
  }

  public static MessageSender getMessageSender() {
    return instance;
  }

  public int sendSMS(String toNumber, String messageBody, String callbackUrl) {
    try {
      new MessageCreator(
              new PhoneNumber(toNumber),
              new PhoneNumber(appSetup.getTwilioNumber()),
              messageBody
      ).setStatusCallback(callbackUrl).create(client);
    } catch (Exception e) {
      e.printStackTrace();
      return 1;
    }
    return 0;
  }
}
package com.twilio.etanotifications.servlets;

import com.twilio.etanotifications.models.Order;
import com.twilio.etanotifications.repositories.OrdersRepository;

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

public class NotificationStatusServlet extends HttpServlet {
  private final OrdersRepository repository;

  @SuppressWarnings("unused")
  public NotificationStatusServlet() {
    this(new OrdersRepository());
  }

  public NotificationStatusServlet(OrdersRepository repository) {
    this.repository = repository;
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    String orderId = request.getParameter("id");
    String status = request.getParameter("MessageStatus");
    int id = Integer.parseInt(orderId);

    Order order = repository.find(id);
    order.setNotificationStatus(status);
    repository.update(order);
  }
}