Using Heroku to deploy servlets and JSPs

Author: David Gonzalez, spring 2020
Modified by: Jeff Offutt, summer 2020, spring 2021, summer 2021

What is Heroku?

This tutorial explains how to deploy and develop a Heroku app through GitHub that can run servlets and JSPs. The tutorial covers creating GitHub and Heroku accounts, deploying serlvets to a Heroku app, developing web apps locally, and using a database to persist data. To work through this tutorial, you will need to be connected to the internet, you will need to be comfortable issuing commands through a command-line terminal interface, and you will need to know how to program servlets. Sections 1-4 are enough to deploy and run Java servlets on Heroku; sections 5 and 6 are more advanced.

Most students spend between 30 minutes and 3 hours to work through this tutorial. Take care not to skip steps. You might find this easier to work with one or more partners to help each other.

We will use a running example Heroku app (collection of servlets), which is deployed here: https://swe432tomcat.herokuapp.com

1. Prelude

To develop web apps, it is important to mentally separate development from deployment. Development includes design, programming, testing, and debugging. Development is usually done locally on the developer’s computer. Deploying is the process of publishing a web app to a server so users can access it, including compiling, installing executables in appropriate folders (or directories in Unix-speak), checking connections to resources such as databases, and creating the URLs that clients will use to run the web app. In a large project, these issues can get quite complex and professional deployers take care of it. Our deployment is small, simple, and student accessible. Heroku is a free hosting service for web apps than can be linked with GitHub to auto-deploy. Heroku also offers development tools so you can test and debug your app locally. This tutorial focuses on servlets, but Heroku supports several other web software technologies.

Please take a moment to explore each concept, technology, command, activity, and action used in this tutorial. We try to strike a balance between brevity and completeness, and welcome feedback and suggestions.

2. Quick reference

These command line statements are here for a quick reminder of details in the tutorial.

A. Redeploying the app by pushing changes to the remote repo

git add .
git commit -m "Cleaned up the UI"
git push

B. Rerunning the app locally after changes

mvn package
heroku local

3. Create GitHub and Heroku accounts

You can create free accounts on both GitHub and Heroku (you do not need to provide any payment info).

Create a GitHub account on https://github.com/.

Create a Heroku account on https://signup.heroku.com/. (Note that Heroku sends an email that may take a few minutes to arrive.)

You may optionally use Heroku’s GitHub student package.

Class note: Your assignments’ repo must be private at all times. To grade your assignments, we must have access to your GitHub repo, thus you must add both the TA and instructor as contributors. We will share our usernames in class or on the discussion board.

4. Deployment: Create a Git repo (repository) and and link it to a Heroku app

Install Git on your local machine from Git’s download site. This will let you use the git command. The setup gives lots of choices that do not make sense unless you are a GitHub expert user. Luckily, the default is usually okay. Here are a few notes:

Once Git is installed, follow steps A—F to bring example provided repo into your Github account:

A. Install the example repo locally in your machine

This code contains all necessary boilerplate for supporting servlets and JSPs in a Heroku app through a terminal (command-line) window:

git clone https://github.com/luminaxster/swe432tomcat.git

This command will copy a directory structure starting with the name “swe432tomcat.” You can change it if you want.

Now is a good time to consider two other names you will need. You will create a GitHub repo name, where your web app source files will be stored, and a Heroku appname, which will be part of the URL address for your programs. Your life will be simpler if you use the same name, and if the name is self-identifying, for example, by including your school user name. I might use “offuttwebapps.” You can also use the same name for the local copy of the repo that you just created.

B. Create an empty repo in your Github

Use the name you designed in the previous step. Your repo name will be shared with partners and instructors.

1. Go to Github, login into your account, find “repositories,” and click “New.” In the “Create a new repository” page, enter the repo name you chose, make it private and let other options default, and click on “create repository.”

2. This will take you to your new repository’s page. Copy the URL under “Quick setup” to access your repo later. It should look like this:

https://github.com/<your_username>/<your_new_repo_name>.git

C. Redirect the local repo to your own repo and save the changes

Remember to replace the URL from step B (https://github.com/<your_username>/<your_new_repo_name>.git) with your own repo’s URL.

1. Set up your remote repo in Git

Only use the following commands once, in a terminal command-line window. If you changed the name of the directory from “swe432tomcat,” use the new name in the “cd” command.

cd swe432tomcat
git init
git remote set-url origin "https://github.com/<your_username>/<your_new_repo_name>.git"

You should not get a response message unless something went wrong.

2. Push your local changes into your remote repo

Reuse the following commands every time you need to send your changes to GitHub, in a terminal command-line window. Run these commands from the same folder.

git add .
git commit -m "message explaining this commit"
#A good first-time message might be: "Initial commit: cloned repo"
git push

The message from push is confusing but you can safely ignore it.

The text in quotes should explain what this current commit is doing and should be clear and explicit to avoid creating maintenance debt. The idea is to document why you made the changes so that everyone who wants to contribute to your project understand, including you after a month not looking at the file.

You will be asked to authenticate with GitHub the first time you push. If you push again without changing anything, you will get the message “Everything up-to-date.” That's a good way to check if your repo is synchronized with your computer.

D. Create a Heroku app

Your Heroku app contains all the servlets and Java classes that you will deploy. The name you choose becomes part of the URL address to your web apps. In the example provided, https://swe432tomcat.herokuapp.com, the app name is “swe432tomcat.” Look at 4.A above for suggestions about the name.

Go to your Heroku dashboard. Click on “New,” then “Create New App,” enter your app name, and click “create app.”

E. Link repo and deploy

Once in your Heroku app web page, select the “deploy” tab:

  1. Set the deployment method to “Github
  2. Click “Connect to GitHub” (Ignore the piplines)
  3. Click “Authorize heroku” on the popup
  4. Enter your <your_new_repo_name> from before, click “Search”
  5. Click “Connect”
  6. Click “Enable Automatic Deploys”
  7. Click on “Deploy Branch” (only this time so you can see the changes immediately)
  8. ... wait while lots of messages scroll by ...
  9. When the message “Your app was successfully deployed.” appears, click “View”

A new browser tab should open that says “Hello Heroku! I am JSP,” and then a list of the servlets you just cloned from luminaxster. You can modify that JSP file in src/main/webapp/index.jsp.

F. Updating your repo and redeploying

Any changes will be redeployed automatically when you push them to your repo using the commands above in 4.C.2. You can modify the provided servlets in the folder ../src/main/java/servlet and deploy a new servlet by placing it in the same folder. Please note that it sometimes takes several minutes for the newly pushed version to be available.

Your servlets will have the URL https://<herokuappname>.herokuapp.com/<servletname> For example, the first servlet in the repo provided has the URL https://swe432tomcat.herokuapp.com/hello. The “View” button from step E.j above runs a JSP, which you can find in swe432tomcat/src/main/webapp/index.jsp. You can run that JSP by going to your Heroku dashboard, clicking on your Heroku app, then clicking on the <Open app> button.

You can add your new servlets to the index list by editing index.jsp. Add a new button to the existing list with the name of the new servlet. Even without changing the JSP, you can run by using the URL https://<herokuappname>.herokuapp.com/<servletname>.

G. Compiling and build errors

Heroku compiles your code when you push it. If the compile fails, you can see the errors through the Heroku dashboard. Click the <Activity> tab, and look for a red message “Build failed.” When you are ready to work through step 5 of this tutorial, you can learn how to develop and compile locally, which is quicker and easier.

Congratulations! You have learned enough to deploy and run your servlets for the class. The rest of this tutorial shows you how to develop and run your servlets locally and how to connect to a database.

5. Development: How to run web apps locally

Before deploying web app to GitHub & Heroku, developers program, debug, and test apps locally. This section explains how to use Apache Maven and Heroku’s command line interface (CLI) to run web apps locally. We walk through installing Maven and Heroku, then running an app locally, updating an app, and continuous loop deployment.

A. Installing Apache Maven

If you have not installed Apache Maven before, get the binaries from the Apache Maven Project, and follow the instructions from Maven’ installation page.

Note: If you are using a Unix-like system (MacOS, Linux, ect.), open a command-line terminal window and add the path to Maven permanently in your bash profile:

vim ~/.bash_profile

Add the following line to allow you to run Maven the terminal:

export PATH=/opt/apache-maven-3.6.3/bin:$PATH

If you have a later version of Maven or installed it in a different place, change the path accordingly.

If your machine runs Windows, add Maven’s path to the PATH property in the system’s environment variables.

Note: You will need to open a new terminal window to reflect the path change.

B. Installing Heroku’s CLI

If you have not installed the Heroku CLI before, download it from the Heroku Dev Center.

By default, the example repo’s Procfile is set for Unix-like machines using “sh” as the shell command in Unix. For Windows, replace the following line in the Procfile:

web: sh target/bin/webapp

with this line:

web: target\bin\webapp.bat

Note: If you are Windows user, do not push your Procfile to your remote repo. That would cause the following error: “targetbinwebapp not found” and then “app crashed” error with code H10 ....

C. Build and run your app

To run an app contained in your repo, look for the file POM.xml in your repo’s root folder. Maven uses this configuration file to build your app so Heroku can run it. Run the following two commands to build and run your app:

mvn package
heroku local

If the commands succeed, you should be able to access your app at: http://localhost:5000.

D. Adding a new servlet

Place your servlet file in the folder src/main/java/servlet folder and add the servlet annotation so your Apache Tomcat knows how to map it:

     
import javax.servlet.annotation.WebServlet;
...
@WebServlet ( name = "servletName", urlPatterns = {"/servicePathName"} )

The line above handles servlet mapping, which makes its servlet instance available at yourServerUrl/servicePathName. That is, the @WebServlet annotation “maps” the name of the servlet, servletName, to the path yourServerUrl/servicePathName, which is appended after the port number 5000. Now you can run your app locally.

Note: If your servlet mapping setup failed or is missing, you will not be able to access the URL localhost:5000/servicePathName or yourWebsite/servicePathName. Instead, the server will return a 404: Not found error. Make sure the @WebServlet annotation is in the servlet Java file and the localhost:5000/servicePathName matches @WebServlet ... urlPatterns = {"/servicePathName"}.

E. Don’t forget to deploy your app to Heroku’s server!

Just because your app works locally, does not mean we can run and grade it. The two most common errors that novices make are:

  1. Forgetting to push the changes to your GitHub repo. When you are ready to share, be sure to push your app to your GitHub repo. It will automatically be deployed through Heroku.
  2. Forgetting to deploy-test the app. Sometimes apps that work correctly on your local machine will NOT work on the server. Common issues are path names, such as the difference between ‘/’ and ‘\’ on Windows and Unix systems, capitalization (Windows treats upper and lower case letters the same, but Unix does not), and external resources such as files and databases. Be sure to run your app on the server to ensure it still works!

6. Persistence: How to use a database with Heroku

Accessing a database may be different on your local machine and on Heroku—this description is for Heroku only. Also note that this is not a general tutorial on using databases from Java programs, but just the specific incantations your program needs to use a Postgres database on Heroku.

Set up to use Postgres on Heroku through the Heroku dashboard. Choose your Tomcat servlet app on your dashboard, go to the Resources tab, click on find add-ons, type Postgres, and Heroku-Postrgres should appear. Select it with the Hobby dev (free) tier.

You can also do this from a terminal command-line window:

heroku addons:create heroku-postgresql:hobby-dev --app <your_heroku_app_name>

Note that <your_heroku_app_name> is the name of your Heroku web application.

This section is quite long and is not necessary to run servlets or JSPs. You only need this to persist data into a database and can skip it otherwise. This section discusses installation, configuring, using the command line interface (CLI) and Java to use the database.

A. Install PostgreSQL

Get Postgres from the Postgres download page.

Windows: The wizard will install the DB, services, and basic tools needed to manage and query the database.

MacOS: Select and install Postgres.app with PostgreSQL 12. Then execute this command in your terminal:

sudo mkdir -p /etc/paths.d &&
echo /Applications/Postgres.app/Contents/Versions/latest/bin | sudo tee /etc/paths.d/postgresapp

Reopen the terminal and enter this command:

which psql

It should return a file system path that looks like this: /Applications/Postgres.app/Contents/Versions/latest/bin/psql.

B. Configure the connection to your remote DB add-ons in Heroku

Java programs access databases using a library package called Java DataBase Connectivity (JDBC). This tutorial does not teach JDBC, but you will need to use it. For your Java applications to access the DB via JDBC, set up the connection as follows:

export JDBC_DATABASE_URL=`heroku run echo \\$JDBC_DATABASE_URL -a <your_heroku_app_name>`

If you use Windows, run the commands separately. The first command will return a URL, which you use in the second command to define the environment variable:

heroku run echo \$JDBC_DATABASE_URL -a <your_heroku_app_name>
setx JDBC_DATABASE_URL "<URL>"

Windows users can also use the URL to create a JDBC_DATABASE_URL property in the system’s environment variables with that URL.

Remember: <your_heroku_app_name> is the name of your Heroku app.

Check that the environment variable was set:

echo $JDBC_DATABASE_URL — In Unix
echo %JDBC_DATABASE_URL% — In Windows

The echo command should return a string like jdbc:postgresql://....

Note: This configuration will be lost when you close the terminal window. Since the credentials are regularly removed, there is no benefit to making it permanent.

C. Connect to the database via a command line interface (CLI)

Enter the DB with the following command:

heroku pg:psql <your_postgresql_add_on_name> --app <your_heroku_app_name>

Remember: <your_heroku_app_name> is the name of your Heroku app, and <your_postgresql_add_on_name> is your Postgres add-on name.

You can get your precise command from the Heroku web page. Login to your account, go to the add-ons dashboard, and select the Postgres add-on created some steps before. Then go to settings > admistration > view credentials > Heroku cli.

Once that command works, you can use the database CLI. In your terminal window, the input should look like this:

<your_heroku_app_name>::DATABASE=>

Now run DB management and query commands like:

     
CREATE TABLE test(id SERIAL PRIMARY KEY, value VARCHAR (50) NOT NULL);
INSERT INTO test(value) VALUES ('a value');
SELECT name FROM test;

D. Connecting to the database within your app: The Database Servlet

Our example project has an example Java class, DatabaseServlet.java, that uses JDBC. This is but one of many possible ways to use databases. The following seven subsections explain how the database servlet was implemented.

1. Manage and query your database

DatabaseServlet.java need the database table to be created before it can run. Do this from your CLI terminal window:

     
CREATE TABLE entries(
  id serial PRIMARY KEY,
  name VARCHAR (50) NOT NULL,
  age INT CHECK (age > 0 AND age < 150) NOT NULL
);

As a check, add a row to the table and then query the data from that row:

INSERT INTO entries (name, age) VALUES ('Logan', 149);
SELECT name, age FROM entries;

2. Add Postgres to your web app

You need to add Postgres to your dependencies in your pom.xml file:

     
<dependencies>
  ...
  <dependency>
    <groupId>org.postgresql</groupId>
     <artifactId>postgresql</artifactId>
    <version>42.2.1</version>
  </dependency>
  ...
</dependencies>

3. Sanity check

To make sure you have the database set up correctly, run the web app locally, from a terminal in your app’s root folder:

mvn package
heroku local

The terminal should show a line like:

INFO: Starting ProtocolHandler ["http-nio-5000"]

Your Tomcat server should be up and running at localhost:5000, and the database servlet should be at localhost:5000/database.

Note: To stop the server from running, press Ctrl+C or close the terminal.

4. Connecting to database from a servlet

     
...
private class EntriesManager{
   private Connection getConnection()
     throws URISyntaxException, SQLException {
       String dbUrl = System.getenv("JDBC_DATABASE_URL");
       return DriverManager.getConnection(dbUrl);
   }
   ...

These statement add Postgres as a dependency and configure the environment variable. The statement String dbUrl = System.getenv("JDBC_DATABASE_URL"); uses the environment variable JDBC_DATABASE_URL. You can check its value with the command echo $JDBC_DATABASE_URL. The statement return DriverManager.getConnection(dbUrl); retrieves the Postgres database driver from the previous URL, then connects to the database with the credentials provided in the URL.

Troubleshooting: You may get an error like: java.sql.SQLException: The url cannot be null. This means “JDBC_DATABASE_URL” is not saved in your profile as described in step a above.

5. Saving data into the database

The following code is taken from method save() in DatabaseServlet.java:
     
...
public boolean save(String name, int age){
       PreparedStatement statement = null;
       try {
         if (connection == null)
            connection = getConnection();
         statement = connection.prepareStatement( "INSERT INTO entries (name, age) values (?, ?)" );
         statement.setString(1, name);
         statement.setInt(2, age);
         statement.executeUpdate();
         return true;
         ...

After getting a connection, the method prepares a statement to insert a row into the table entries. The order of values is determined by the tuple (name, age). The statements statement.setString(1, name) and statement.setInt(2, age) maps the value 1 to variable name and value 2 to variable age. Finally, statement.executeUpdate(); inserts the row.

Important: Prepared statements prevent some types of SQL injection attacks that other API methods allow.

6. Querying data from the database

The following code from method getAllAsHTMLTable() retrieves all names and ages from the entries table, then displays them in an HTML table.

     
public String getAllAsHTMLTable(){
       Statement statement = null;
       ResultSet entries = null;
       StringBuilder htmlOut = new StringBuilder();
       try {
         if (connection == null)
            connection = getConnection();
         statement = connection.createStatement();
         entries = statement.executeQuery( SELECT "+Data.NAME.name()+", "+Data.AGE.name()+" FROM entries");
 
         while (entries.next()) {
          htmlOut.append("<tr><td>");
          htmlOut.append(Jsoup.clean(entries.getString(1), Whitelist.basic())); //name
          htmlOut.append("</td><td>");
          htmlOut.append(entries.getInt(2)); //age
          htmlOut.append("</td></tr>");
         }
         if(htmlOut.length() == 0){
           htmlOut.append("<tr><td> no entries</td></tr>");
         }
       }catch(URISyntaxException uriSyntaxException){
       ...

After getting a connection to the database, executing the query returns an iterable ResultSet, which is stored in the variable entries. The while loop prints all name and age pairs in entries.

Important: Using only executeQuery() to make querys prevents some types of SQL injection attacks that other API methods allow. For instance, using executeUpdate() to make queries can allow attackers to delete database tables by appending the statement delete update ; delete from entries.

7. Important: Avoid cross-side scripting (XSS) attacks

A common way to attack web apps is to inject malicious code into data that originates as user inputs. This is call cross-site scripting (XSS.) Since it is common to generate HTML content from user data, an attacker may add executable code that will trigger in the page. For example, adding <script>function xss(){location.href= 'https://www.google.com'} </script><button onclick="xss()">click me</button> as a name can add a malicious button that takes users to google.com when clicked. The database fails because the table column does not accept more than 50 characters. However, <script>function x(){location.href='s'}()</script> fits. A good way to avoid this (illustrated in our example) is to use Jsoup to sanitize data when accepting user inputs, and when sending them back to your front-end. The database example uses Jsoup.clean() when getting row’s names from the database, and when capturing strings from the users, as in: String name = Jsoup.clean(request.getParameter(Data.NAME.name()), Whitelist.basic());.

Always use sanitized user inputs to assemble statements, and use prepared statements when concatenating user inputs in your queries or updates.

8. Saving data into a database from a servlet

The following code from the doPost() method adds new data into the databse.
     
... doPost(...
   EntriesManager entriesManager = new EntriesManager();
 
   boolean ok = entriesManager.save(name, age);
   String saveStatusHTML = "<p>"+(ok? "Entry added.":"Entry was not added.")+"</p>";
   PrintHead(out);
   PrintEntriesBody(out, saveStatusHTML, entriesManager.getAllAsHTMLTable());
   PrintTail(out);

The servlet uses database persistence via the EntriesManager instance to save (save(name, age)) a new entry, then renders all the entries in the database into an HTML table with getAllAsHTMLTable().

7. Resources

For more details about how to create a Tomcat setup from scratch, go to the Dev Center guide on how to Create a Java Web Application using Embedded Tomcat.