Skip to contentSkip to navigationSkip to topbar
On this page

Workflow Automation with Java and Servlets


One of the more abstract concepts you'll handle when building your business is what the workflow will look like.

At its core, setting up a standardized workflow is about enabling your service providers (agents, hosts, customer service reps, administrators, and the rest of the gang) to better serve your customers.

To illustrate a very real-world example, today we'll build a Java and Servlets webapp for finding and booking vacation properties — tentatively called Airtng.

Here's how it'll work:

  1. A host creates a vacation property listing
  2. A guest requests a reservation for a property
  3. The host receives an SMS notifying them of the reservation request. The host can either Accept or Reject the reservation
  4. The guest is notified whether a request was rejected or accepted

Learn how Airbnb used Twilio SMS to streamline the rental experience for 60M+ travelers around the world.(link takes you to an external page)


Workflow Building Blocks

workflow-building-blocks page anchor

We'll be using the Twilio REST API to send our users messages at important junctures. Here's a bit more on our API:

  • Sending Messages with Twilio API

User and Session Management

user-and-session-management page anchor

For this workflow to work, we need to have a user model and allow logins.

User and Session Management

user-and-session-management-1 page anchor

src/main/java/org/twilio/airtng/models/User.java

1
package org.twilio.airtng.models;
2
3
import javax.persistence.*;
4
import java.util.ArrayList;
5
import java.util.List;
6
7
@Entity
8
@Table(name = "users")
9
public class User {
10
@Id
11
@GeneratedValue(strategy = GenerationType.IDENTITY)
12
@Column(name = "id")
13
private long id;
14
15
@Column(name = "name")
16
private String name;
17
18
@Column(name = "email")
19
private String email;
20
21
@Column(name = "password")
22
private String password;
23
24
@Column(name = "phone_number")
25
private String phoneNumber;
26
27
@OneToMany(mappedBy="user")
28
private List<Reservation> reservations;
29
30
@OneToMany(mappedBy="user")
31
private List<VacationProperty> vacationProperties;
32
33
public User() {
34
this.reservations = new ArrayList<>();
35
this.vacationProperties = new ArrayList<>();
36
}
37
38
public User(
39
String name,
40
String email,
41
String password,
42
String phoneNumber) {
43
this();
44
this.name = name;
45
this.email = email;
46
this.password = password;
47
this.phoneNumber = phoneNumber;
48
}
49
50
public long getId() {
51
return id;
52
}
53
54
public void setId(long id) {
55
this.id = id;
56
}
57
58
public String getName() {
59
return name;
60
}
61
62
public void setName(String name) {
63
this.name = name;
64
}
65
66
public String getEmail() {
67
return email;
68
}
69
70
public void setEmail(String email) {
71
this.email = email;
72
}
73
74
public String getPassword() {
75
return password;
76
}
77
78
public void setPassword(String password) {
79
this.password = password;
80
}
81
82
public String getPhoneNumber() {
83
return phoneNumber;
84
}
85
86
public void setPhoneNumber(String phoneNumber) {
87
this.phoneNumber = phoneNumber;
88
}
89
90
public void addReservation(Reservation reservation) {
91
92
if (this.reservations.add(reservation) && reservation.getUser() != this) {
93
reservation.setUser(this);
94
}
95
}
96
97
public void removeReservation(Reservation reservation) {
98
if (this.reservations.remove(reservation) && reservation.getUser() == this) {
99
reservation.setUser(null);
100
}
101
}
102
103
public void addVacationProperty(VacationProperty vacationProperty) {
104
105
if (this.vacationProperties.add(vacationProperty) && vacationProperty.getUser() != this) {
106
vacationProperty.setUser(this);
107
}
108
}
109
110
public void removeVacationProperty(VacationProperty vacationProperty) {
111
if (this.reservations.remove(vacationProperty) && vacationProperty.getUser() == this) {
112
vacationProperty.setUser(null);
113
}
114
}
115
116
}

Next, let's look at the Vacation Property model.


In order to build a true vacation rental company we'll need a way to create a property rental listing.

The VacationProperty model belongs to the User who created it (we'll call this user the host moving forward) and contains only two properties: a description and an imageUrl.

The model has two associations implemented: there are many reservations in it and many users can make those reservations.

src/main/java/org/twilio/airtng/models/VacationProperty.java

1
package org.twilio.airtng.models;
2
3
import javax.persistence.*;
4
import java.util.ArrayList;
5
import java.util.List;
6
7
@Entity
8
@Table(name = "vacation_properties")
9
public class VacationProperty {
10
11
@javax.persistence.Id
12
@GeneratedValue(strategy = GenerationType.IDENTITY)
13
@Column(name = "id")
14
private long id;
15
16
@Column(name = "description")
17
private String description;
18
19
@Column(name = "image_url")
20
private String imageUrl;
21
22
@OneToMany(mappedBy = "vacationProperty")
23
private List<Reservation> reservations;
24
25
@ManyToOne(fetch = FetchType.LAZY)
26
@JoinColumn(name = "user_id")
27
private User user;
28
29
public VacationProperty() {
30
this.reservations = new ArrayList<Reservation>();
31
}
32
33
public VacationProperty(String description, String imageUrl, User user) {
34
this.description = description;
35
this.imageUrl = imageUrl;
36
this.user = user;
37
}
38
39
public long getId() {
40
return id;
41
}
42
43
public void setId(long id) {
44
this.id = id;
45
}
46
47
public String getDescription() {
48
return description;
49
}
50
51
public void setDescription(String description) {
52
this.description = description;
53
}
54
55
public String getImageUrl() {
56
return imageUrl;
57
}
58
59
public void setImageUrl(String imageUrl) {
60
this.imageUrl = imageUrl;
61
}
62
63
public User getUser() {
64
return this.user;
65
}
66
67
public void setUser(User user) {
68
this.user = user;
69
}
70
71
public void addReservation(Reservation reservation) {
72
73
if (this.reservations.add(reservation) && reservation.getVacationProperty() != this) {
74
reservation.setVacationProperty(this);
75
}
76
}
77
78
public void removeReservation(Reservation reservation) {
79
if (this.reservations.remove(reservation) && reservation.getVacationProperty() == this) {
80
reservation.setVacationProperty(null);
81
}
82
}
83
}

Next, let's look at the all-important Reservation model.


The Reservation model is at the center of the workflow for this application. It is responsible for keeping track of:

  • The guest who performed the reservation
  • The vacation property the guest is requesting (and associated host)
  • The status of the reservation: pending, confirmed, or rejected

src/main/java/org/twilio/airtng/models/Reservation.java

1
package org.twilio.airtng.models;
2
3
import javax.persistence.*;
4
5
@Entity
6
@Table(name = "reservations")
7
public class Reservation {
8
9
@javax.persistence.Id
10
@GeneratedValue(strategy = GenerationType.IDENTITY)
11
@Column(name = "id")
12
private long id;
13
14
@Column(name = "message")
15
private String message;
16
17
@Column(name = "status")
18
private Integer status;
19
20
@ManyToOne(fetch = FetchType.LAZY)
21
@JoinColumn(name = "user_id")
22
private User user;
23
24
@ManyToOne(fetch = FetchType.LAZY)
25
@JoinColumn(name = "vacation_property_id")
26
private VacationProperty vacationProperty;
27
28
public Reservation() {
29
}
30
31
public Reservation(String message, VacationProperty vacationProperty, User user) {
32
this();
33
this.message = message;
34
this.vacationProperty = vacationProperty;
35
this.user = user;
36
this.setStatus(Status.Pending);
37
}
38
39
public long getId() {
40
return id;
41
}
42
43
public void setId(long id) {
44
this.id = id;
45
}
46
47
public String getMessage() {
48
return message;
49
}
50
51
public void setMessage(String message) {
52
this.message = message;
53
}
54
55
public Status getStatus() {
56
return Status.getType(this.status);
57
}
58
59
public void setStatus(Status status) {
60
if (status == null) {
61
this.status = null;
62
} else {
63
this.status = status.getValue();
64
}
65
}
66
67
public VacationProperty getVacationProperty() {
68
return vacationProperty;
69
}
70
71
public void setVacationProperty(VacationProperty vacationProperty) {
72
this.vacationProperty = vacationProperty;
73
}
74
75
public User getUser() {
76
return this.user;
77
}
78
79
public void setUser(User user) {
80
this.user = user;
81
}
82
83
public void reject() {
84
this.setStatus(Status.Rejected);
85
}
86
87
public void confirm() {
88
this.setStatus(Status.Confirmed);
89
}
90
91
public enum Status {
92
93
Pending(0), Confirmed(1), Rejected(2);
94
95
private int value;
96
97
Status(int value) {
98
this.value = value;
99
}
100
101
public int getValue() {
102
return value;
103
}
104
105
@Override
106
public String toString() {
107
switch (value) {
108
case 0:
109
return "pending";
110
case 1:
111
return "confirmed";
112
case 2:
113
return "rejected";
114
}
115
return "Value out of range";
116
}
117
118
119
public static Status getType(Integer id) {
120
121
if (id == null) {
122
return null;
123
}
124
125
for (Status status : Status.values()) {
126
if (id.equals(status.getValue())) {
127
return status;
128
}
129
}
130
throw new IllegalArgumentException("No matching type for value " + id);
131
}
132
}
133
}

Next up, we'll show how our new application will create a new reservation.


The reservation creation form holds only one field: the message that will be sent to the host when reserving one of her properties.

The rest of the information necessary to create a reservation is taken from the logged in user and the relationship between a property and its owner. Our base generic Repository(link takes you to an external page) is in charge of handling entity insertion.

A reservation is created with a default status of pending. This lets our application react to a host rejecting or accepting a reservation request.

Create a new reservation

create-a-new-reservation page anchor

src/main/java/org/twilio/airtng/servlets/ReservationServlet.java

1
package org.twilio.airtng.servlets;
2
3
import org.twilio.airtng.lib.notifications.SmsNotifier;
4
import org.twilio.airtng.lib.servlets.WebAppServlet;
5
import org.twilio.airtng.lib.web.request.validators.RequestParametersValidator;
6
import org.twilio.airtng.models.Reservation;
7
import org.twilio.airtng.models.User;
8
import org.twilio.airtng.models.VacationProperty;
9
import org.twilio.airtng.repositories.ReservationRepository;
10
import org.twilio.airtng.repositories.UserRepository;
11
import org.twilio.airtng.repositories.VacationPropertiesRepository;
12
13
import javax.servlet.ServletException;
14
import javax.servlet.http.HttpServletRequest;
15
import javax.servlet.http.HttpServletResponse;
16
import java.io.IOException;
17
18
public class ReservationServlet extends WebAppServlet {
19
20
private final VacationPropertiesRepository vacationPropertiesRepository;
21
private final ReservationRepository reservationRepository;
22
private UserRepository userRepository;
23
private SmsNotifier smsNotifier;
24
25
public ReservationServlet() {
26
this(new VacationPropertiesRepository(), new ReservationRepository(), new UserRepository(), new SmsNotifier());
27
}
28
29
public ReservationServlet(VacationPropertiesRepository vacationPropertiesRepository, ReservationRepository reservationRepository, UserRepository userRepository, SmsNotifier smsNotifier) {
30
super();
31
this.vacationPropertiesRepository = vacationPropertiesRepository;
32
this.reservationRepository = reservationRepository;
33
this.userRepository = userRepository;
34
this.smsNotifier = smsNotifier;
35
}
36
37
@Override
38
public void doGet(HttpServletRequest request, HttpServletResponse response)
39
throws ServletException, IOException {
40
41
VacationProperty vacationProperty = vacationPropertiesRepository.find(Long.parseLong(request.getParameter("id")));
42
request.setAttribute("vacationProperty", vacationProperty);
43
request.getRequestDispatcher("/reservation.jsp").forward(request, response);
44
}
45
46
@Override
47
public void doPost(HttpServletRequest request, HttpServletResponse response)
48
throws ServletException, IOException {
49
50
super.doPost(request, response);
51
52
String message = null;
53
VacationProperty vacationProperty = null;
54
55
if (isValidRequest()) {
56
message = request.getParameter("message");
57
String propertyId = request.getParameter("propertyId");
58
vacationProperty = vacationPropertiesRepository.find(Long.parseLong(propertyId));
59
60
User currentUser = userRepository.find(sessionManager.get().getLoggedUserId(request));
61
Reservation reservation = reservationRepository.create(new Reservation(message, vacationProperty, currentUser));
62
smsNotifier.notifyHost(reservation);
63
response.sendRedirect("/properties");
64
}
65
preserveStatusRequest(request, message, vacationProperty);
66
request.getRequestDispatcher("/reservation.jsp").forward(request, response);
67
}
68
69
@Override
70
protected boolean isValidRequest(RequestParametersValidator validator) {
71
72
return validator.validatePresence("message");
73
}
74
75
private void preserveStatusRequest(
76
HttpServletRequest request,
77
String message, Object vacationProperty) {
78
request.setAttribute("message", message);
79
request.setAttribute("vacationProperty", vacationProperty);
80
}
81
}

Now let's look at how the system will notify a host when a new reservation request is submitted.


When a reservation is created for a property we want to notify the owner that someone has made a reservation.

We use an abstraction called SmsNotifier which under the hood uses another abstraction called Sender. Here is where we use Twilio's Rest API to send an SMS message to the host using your Twilio phone number(link takes you to an external page).

src/main/java/org/twilio/airtng/lib/sms/Sender.java

1
package org.twilio.airtng.lib.sms;
2
3
import com.twilio.http.TwilioRestClient;
4
import com.twilio.rest.api.v2010.account.MessageCreator;
5
import com.twilio.type.PhoneNumber;
6
import org.twilio.airtng.lib.Config;
7
8
public class Sender {
9
10
private final TwilioRestClient client;
11
12
public Sender() {
13
client = new TwilioRestClient.Builder(Config.getAccountSid(), Config.getAuthToken()).build();
14
}
15
16
public Sender(TwilioRestClient client) {
17
this.client = client;
18
}
19
20
public void send(String to, String message) {
21
new MessageCreator(
22
new PhoneNumber(to),
23
new PhoneNumber(Config.getPhoneNumber()),
24
message
25
).create(client);
26
}
27
28
}

The next step shows how to handle a host accepting or rejecting a request - let's continue.


Handle Incoming Messages

handle-incoming-messages page anchor

Let's take a closer look at the ReservationConfirmation servlet. This servlet handles our incoming Twilio request and does three things:

  1. Check for a pending reservation from the incoming user.
  2. Update the status of the reservation.
  3. Respond to the host and send notification to the guest.

Incoming Twilio Requests

incoming-twilio-requests page anchor

In the Twilio console(link takes you to an external page), you should change the 'A Message Comes In' webhook to call your application's endpoint in the route /reservation-confirmation:

SMS Webhook.

One way to expose your machine to the world during development is to use ngrok(link takes you to an external page). Your URL for the SMS web hook on your phone number should look something like this:

http://<subdomain>.ngrok.io/reservation-confirmation

An incoming request from Twilio comes with some helpful parameters, including the From phone number and the message Body.

We'll use the From parameter to lookup the host and check if they have any pending reservations. If they do, we'll use the message body to check for an 'accept' or 'reject'.

Finally, we update the reservation status and use the SmsNotifier abstraction to send an SMS to the guest with the news.

Webhook for handling Host's decision

1
package org.twilio.airtng.servlets;
2
3
import com.twilio.twiml.MessagingResponse;
4
import com.twilio.twiml.TwiMLException;
5
import org.twilio.airtng.lib.helpers.TwiMLHelper;
6
import org.twilio.airtng.lib.notifications.SmsNotifier;
7
import org.twilio.airtng.lib.servlets.WebAppServlet;
8
import org.twilio.airtng.models.Reservation;
9
import org.twilio.airtng.models.User;
10
import org.twilio.airtng.repositories.ReservationRepository;
11
import org.twilio.airtng.repositories.UserRepository;
12
13
import javax.servlet.ServletException;
14
import javax.servlet.http.HttpServletRequest;
15
import javax.servlet.http.HttpServletResponse;
16
import java.io.IOException;
17
18
public class ReservationConfirmationServlet extends WebAppServlet {
19
20
private UserRepository userRepository;
21
private ReservationRepository reservationRepository;
22
private SmsNotifier smsNotifier;
23
24
public ReservationConfirmationServlet() {
25
this(new UserRepository(), new ReservationRepository(), new SmsNotifier());
26
}
27
28
public ReservationConfirmationServlet(UserRepository userRepository, ReservationRepository reservationRepository, SmsNotifier smsNotifier) {
29
super();
30
this.userRepository = userRepository;
31
this.reservationRepository = reservationRepository;
32
this.smsNotifier = smsNotifier;
33
}
34
35
@Override
36
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
37
38
String phone = request.getParameter("From");
39
String smsContent = request.getParameter("Body");
40
41
String smsResponseText = "Sorry, it looks like you don't have any reservations to respond to.";
42
43
try {
44
User user = userRepository.findByPhoneNumber(phone);
45
Reservation reservation = reservationRepository.findFirstPendantReservationsByUser(user.getId());
46
if (reservation != null) {
47
if (smsContent.contains("yes") || smsContent.contains("accept"))
48
reservation.confirm();
49
else
50
reservation.reject();
51
reservationRepository.update(reservation);
52
53
smsResponseText = String.format("You have successfully %s the reservation", reservation.getStatus().toString());
54
smsNotifier.notifyGuest(reservation);
55
}
56
57
respondSms(response, smsResponseText);
58
59
} catch (Exception e) {
60
throw new RuntimeException(e);
61
}
62
}
63
64
private void respondSms(HttpServletResponse response, String message)
65
throws IOException, TwiMLException {
66
MessagingResponse twiMLResponse = TwiMLHelper.buildSmsRespond(message);
67
response.setContentType("text/xml");
68
response.getWriter().write(twiMLResponse.toXml());
69
}
70
}

Next up, let's see how we will respond to Twilio.


Finally, we'll use Twilio's TwiML as a response to Twilio's server instructing it to send SMS notification message to the host.

We'll be using the Message(link takes you to an external page) verb to instruct Twilio's server that it should send the message.

src/main/java/org/twilio/airtng/servlets/ReservationConfirmationServlet.java

1
package org.twilio.airtng.servlets;
2
3
import com.twilio.twiml.MessagingResponse;
4
import com.twilio.twiml.TwiMLException;
5
import org.twilio.airtng.lib.helpers.TwiMLHelper;
6
import org.twilio.airtng.lib.notifications.SmsNotifier;
7
import org.twilio.airtng.lib.servlets.WebAppServlet;
8
import org.twilio.airtng.models.Reservation;
9
import org.twilio.airtng.models.User;
10
import org.twilio.airtng.repositories.ReservationRepository;
11
import org.twilio.airtng.repositories.UserRepository;
12
13
import javax.servlet.ServletException;
14
import javax.servlet.http.HttpServletRequest;
15
import javax.servlet.http.HttpServletResponse;
16
import java.io.IOException;
17
18
public class ReservationConfirmationServlet extends WebAppServlet {
19
20
private UserRepository userRepository;
21
private ReservationRepository reservationRepository;
22
private SmsNotifier smsNotifier;
23
24
public ReservationConfirmationServlet() {
25
this(new UserRepository(), new ReservationRepository(), new SmsNotifier());
26
}
27
28
public ReservationConfirmationServlet(UserRepository userRepository, ReservationRepository reservationRepository, SmsNotifier smsNotifier) {
29
super();
30
this.userRepository = userRepository;
31
this.reservationRepository = reservationRepository;
32
this.smsNotifier = smsNotifier;
33
}
34
35
@Override
36
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
37
38
String phone = request.getParameter("From");
39
String smsContent = request.getParameter("Body");
40
41
String smsResponseText = "Sorry, it looks like you don't have any reservations to respond to.";
42
43
try {
44
User user = userRepository.findByPhoneNumber(phone);
45
Reservation reservation = reservationRepository.findFirstPendantReservationsByUser(user.getId());
46
if (reservation != null) {
47
if (smsContent.contains("yes") || smsContent.contains("accept"))
48
reservation.confirm();
49
else
50
reservation.reject();
51
reservationRepository.update(reservation);
52
53
smsResponseText = String.format("You have successfully %s the reservation", reservation.getStatus().toString());
54
smsNotifier.notifyGuest(reservation);
55
}
56
57
respondSms(response, smsResponseText);
58
59
} catch (Exception e) {
60
throw new RuntimeException(e);
61
}
62
}
63
64
private void respondSms(HttpServletResponse response, String message)
65
throws IOException, TwiMLException {
66
MessagingResponse twiMLResponse = TwiMLHelper.buildSmsRespond(message);
67
response.setContentType("text/xml");
68
response.getWriter().write(twiMLResponse.toXml());
69
}
70
}

Congrats! You've just learned how to automate your workflow with Twilio SMS.

Next, let's look at some other interesting features you might want to add to your application.


Like Twilio? Like Java? You're in the right place. Here are a couple other excellent 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.

Need some help?

Terms of service

Copyright © 2024 Twilio Inc.