Build a Continuous Deployment Pipeline in Java with Travis CI

Today I’m here to talk about continuous deployment in Java. In this tutorial you’ll start with the basics, learning about CI/CD and the benefits. Then you’ll dive into the practical part of the post. I’ll show you how to set up continuous integration and then continuous deployment for a Spring Boot app, in a simple and quick way, and using only free resources. Before wrapping up, I’ll even give you a bonus: a quick guide on how to add a feature flag to your app, so you can change how it behaves in production without having to change code and redeploy. Let’s get started.

What is CI/CD?

CI stands for continuous integration, while CD means continuous deployment. In continuous integration, developers integrate their work often, at least once a day, but typically many times a day. They do this by submitting their work to a central, shared repository, where a tool—a CI server—builds their code and runs the automated tests, often along with other optional steps.

Continuous deployment goes one step beyond and also automates code deployment. That means that every time a merge to the main line happens successfully—that is, the code builds, all tests pass, etc.—the product is actually deployed to production, typically with no human intervention.

The Benefits of CI/CD

The benefits of continuous integration and continuous deployment boil down to short feedback cycles. Before continuous integration, it was common for developers to work in isolated silos for many weeks or even months without merging their work to the main line. Such a scenario invariably resulted in problems when it was time to integrate. Their work would’ve diverged so drastically that insanely complex merge and logical conflicts would occur.

That was when continuous integration was created.

With the basics out of the way, it’s time for you to roll up your sleeves and get to work. I’ll walk you through how to set up continuous deployment in Java using free, easily accessible resources. By the end of the post, you’ll know how to leverage those resources to configure CI/CD for your applications and reap all of the benefits that those practices provide.

Requirements for CI/CD in Java

Let’s begin by reviewing what you’ll need to follow along with me in this tutorial. I’ll use Java 8 and IntelliJ IDEA, it’ll be easier to follow if you can match me, though you can certainly use any IDE. I’ll also use Maven as my build system, but feel free to use Gradle if you prefer. You’ll also need:

For the feature flag part of the tutorial, you’ll also need a Split account. Getting set up with Split is easy and free, and I’ll show you how to do it later in this post.

To get started, go to the OpenWeather API website. When you’re there, create an account and log in to retrieve your free API key. Click on your account name in the upper right, choose My API Keys copy your API key and then store it somewhere safe. You’re going to need that key in order to make the sample app work.

Load Up the Spring Boot Sample App

For brevity’s sake, I won’t walk you through creating an application from scratch. Instead, you’ll start by downloading a pre-built application and then do your work on top of that. The application is mostly the same as the one you built in a past post. Reading that post isn’t required to understand this one, though.

The application is a simple app that shows the current time in Detroit, Michigan. After you’re done setting up continuous deployment for the app, you’ll add a new feature to allow users to specify the city and country for which they want to see the current weather and then use a feature flag to hide that feature from most users.

You can find the app in this GitHub repo. Clone the repository, or download it as a .zip file. Place the contents of the repository in an easily accessible folder.

NOTE: There are two tags in the git repo: weather-app tag is the application’s original state – without feature flags and without CI/CD integration. All you need is the OpenWeather API key. The weather-app-with-feature-flags tag is the completed example for which you’ll need to set a Split API key as well as the encrypted Heroku API key for the Travis CI configuration. I’ll walk you through all of that below.

Run the Spring Boot Sample App

The next step is to run the app. Using IntelliJ IDEA, or your IDE of choice, open the pom.xml file from the project. You’ll be asked whether to open it as a regular file or as a project. Choose to open as a project.

At this stage, trying to run the application won’t work. That’s because it makes use of the @Value Spring Boot annotation, and the value has to be passed to the application somehow.

You’ll do that by adding an environment variable to IntelliJ IDEA. Go to Run, then Edit Configurations.

You should have a configuration named after the project. On the Configuration tab, locate the Environment Variables field, and fill it with OPENWEATHERMAP_API_KEY=<YOUR-API-KEY>, replacing <YOUR-API-KEY> with your actual OpenWeather API key:

Then you can click OK and run the app. Go to http://localhost:8080/current-weather, and you’ll see the app working:

For the next step, you’ll get your repo ready. The way you’ll do that depends on whether you’ve cloned or downloaded the app as a .zip.

If you’ve cloned the repo, you’ll need to remove the existing remote that points to the original repository. Do that by opening a terminal, navigating to the root of the project’s folder, and running this:

git remote remove origin
Code language: Arduino (arduino)

If, on the other hand, you’ve downloaded the app’s code as a .zip file, you’ll have to create a repo by running git init inside the project’s folder.

The next steps will be the same for both scenarios. First, create a new repository on GitHub. Then, go to its main page and copy the link used for cloning. Go back to your terminal, and run the following commands, making sure to use your actual GitHub username and repository name instead of the placeholders below:

git remote add origin https://github.com/your-user-name/your-repo-name.git git push -u origin master
Code language: C# (cs)

Get Started with Travis CI

Now that you have a simple working Spring Boot application, you’re ready to start implementing CI/CD using Travis CI.

To start, create the configuration file that will teach Travis how to build your app. Name the file .travis.yml, and place it in the root of your project. Then add the following code to it:

language: java jdk: openjdk8 before_install: - chmod +x mvnw
Code language: Markdown (markdown)

Commit this file, and push this to your GitHub repo.

The next step is to enable Travis for this particular repository. Go to the Travis CI site, and sign up or sign in using your GitHub account. After you’re logged in, you’ll see on the left side of the screen the list of your current repositories configured with Travis. Click on the + to add a new repo. You’ll see a list of all your repos. Locate the correct one, and enable Travis CI for it by clicking on the slider switch.

Then click on its name. You’ll be taken to the repo’s config page. You have one step left before you can build your project using Travis CI. Remember the OpenWeather API key? That’s right—you need a way to securely pass that value to Travis CI. You’ll do that by creating an environment variable.

Click on More options and then on Settings, as in the image below. Then go to the Environment Variables section, which should look like this:

Follow the order above to configure the following:

  1. Type OPENWEATHERMAP_API_KEY
  2. Paste your key’s value
  3. Leave it as default
  4. Don’t activate
  5. Click Add

With that out of the way, you’re ready to trigger your first build.

Go back to the project’s page; click on More options and then on Trigger build:

Then, click on Trigger custom build, and wait while Travis CI builds your project.

Congratulations! You’ve successfully set up continuous integration for your project! Let’s now see how you can use Travis CI to deploy your app.

Continuous Deployment to Heroku

Travis CI allows you to deploy to many different platforms. The list of supported providers includes the usual suspects, such as AWS, Azure, and Google Cloud, but goes way beyond them.

For this tutorial, we’ll show you how to deploy to Heroku. As soon as this option is correctly configured, Travis CI will automatically deploy your app after each successful build on the main branch.

First, go to Heroku, and sign up for a free account, if you don’t already have one. Then log in to your account, and create a new app:

On the new page, choose a name for your app, pick a deployment region, and click on Create app:

For the next step, you’ll need to retrieve your Heroku API key. Go to the Manage Account page. Scroll down the page until you get to the API key, and then click Reveal:

Copy the key’s value. You’ll use the Travis cli below to encrypt it and keep it secure.The only step left is to add the Heroku configuration to your .travis.yml file:

deploy: provider: heroku api_key: secure: YOUR-ENCRYPTED-API-KEY

Replace YOUR-ENCRYPTED-API-KEY with the value of your actual Heroku API key. In order to encrypt it, you’ll need the Travis CLI.

Start by opening a terminal and installing the Travis gem:

gem install travis
Code language: SQL (Structured Query Language) (sql)

After the installation is complete, navigate to your project’s directory, and run the following command:

travis encrypt <your-heroku-api-key> --add deploy.api_key --org
Code language: C# (cs)

This might take awhile, but you’ll eventually see a message with the resulting encrypted data. Copy it and place it in the .travis.yml file. Your complete file should now look like this:

language: java jdk: openjdk8 before_install: - chmod +x mvnw deploy: provider: heroku api_key: secure: <encrypted value>
Code language: C# (cs)
NOTE: By default, Travis CI will try to deploy to Heroku using an application name that matches the repo name. If you’ve named your Heroku application something different, you’ll need to add an app: <app name> element below deploy: shown above.

After you save, commit, and push your .travis.yml file, your app will be deployed to Heroku.

Access Your Deployed App

You can now access your app. On the app’s page, click Open app.

Wait, why’d you get an error? If you’ve said “OpenWeather API,” then you’re right. You had to provide that value to the application, both locally and also when configuring Travis CI. Heroku is no different. So let’s configure an environment variable in Heroku.

On the app’s page, click Settings, and then scroll the page until you see the Config Vars area. Click Reveal Config Vars. This is what you’ll see:

You know the drill by now. Type API_OPENWEATHERMAP_KEY into the KEY field, and paste your OpenWeather API key into the VALUE field. Click Add and you’re done!

Now, click again on Open app. A new tab will open with an error message. Don’t worry, though. This time, it’s an expected error. Go to the address bar, and add /current-weather to the address. Load that page, and you’ll see your app in all its glory:

Add Feature Flags to Your Java App

Now that you have a working app with continuous deployment set up, imagine you want to add a new feature, but only deploy it to a small set of internal testers (testing in production!) or maybe you’re ready to release it as a canary to a subset of your users. Enter feature flags!

Get Your Split API Key

You’ll use Split to manage your feature flag. Start by signing up for a forever-free account, you could also click the link at the top-right of this page.

Once you’re logged in, get your API key by going to the Split dashboard, clicking on the Workspace button—the one with DE on the top left corner—and then clicking on Admin Settings.

NOTE: If you’re new to using Split and/or on the free tier, the button in the upper left will say DE for default. If you’ve set up multiple workspaces, then the button will be labeled with the first two letters of the workspace name.

On this new page, you’ll see several keys. For this tutorial, you’ll pick an SDK key for production.

Copy the key, and store it somewhere safe.

Obtain the Split Java SDK

The next step is adding the Java Split SDK to your code, and it’s incredibly easy to do. It’s just another dependency to add to your .pom.xml file:

<dependency> <groupId>io.split.client</groupId> <artifactId>java-client</artifactId> <version>3.3.4</version> </dependency>
Code language: Django (django)

Add Feature Flags to Your Java Code

Making the feature flag work will require some changes to your code. First of all, you’ll need to change the template for the application, so it shows the name of the city and its country. Open the src/main/resources/templates/current-weather.html file, and update its contents to this (I’m omitting the basic HTML tags):

<div> <h3>City</h3> <p th:text="${currentWeather.city}">City</p> <h3>Current Country</h3> <p th:text="${currentWeather.country}">Country</p> <h3>Current Conditions</h3> <p th:text="${currentWeather.description}">Description</p> <h3>Temperature</h3> <p th:text="${currentWeather.temperature} + ' &#8457;'">Temperature</p> <h3>Feels Like</h3> <p th:text="${currentWeather.feelsLike} + ' &#8457;'">Feels Like</p> <h3>Wind Speed</h3> <p th:text="${currentWeather.windSpeed} + ' mph'"></p> </div>
Code language: Django (django)

Also, open the CurrentWeather class (the model), and change its constructor, so it receives two additional parameters—city and country:

public CurrentWeather( String city, String country, String description, BigDecimal temperature, BigDecimal feelsLike, BigDecimal windSpeed ) { this.city = city; this.country = country; this.description = description; this.temperature = temperature; this.feelsLike = feelsLike; this.windSpeed = windSpeed; }
Code language: Arduino (arduino)

Don’t forget to add the respective fields to the class.

Now, you’ll need to add some code that will instantiate a Split client. Open the WeatherApplicationConfig class, and add the following code to it:

@Value("#{ @environment['split.api-key'] }") private String splitApiKey; @Bean public SplitClient splitClient() throws Exception { SplitClientConfig config = SplitClientConfig.builder() .setBlockUntilReadyTimeout(10000) .enableDebug() .build(); SplitFactory splitFactory = SplitFactoryBuilder.build(splitApiKey, config); SplitClient client = splitFactory.client(); client.blockUntilReady(); return client; }
Code language: Arduino (arduino)


As you can see, there’s now another environmental value used here. Use the IntelliJ options to create a new environment variable containing the value of your Split API key.

Next, go to the CurrentWeatherController, and replace all of its contents with this:

@Controller public class CurrentWeatherController { private final WeatherService weatherService; private final SplitClient splitClient; public CurrentWeatherController( WeatherService weatherService, SplitClient splitClient ) { this.weatherService = weatherService; this.splitClient = splitClient; } @GetMapping(value = {"/current-weather", "/current-weather/{location}"} ) public String getCurrentWeather( @PathVariable(name="location") Optional<String> location, Model model ) { String city; String country; String treatment = splitClient.getTreatment("anonymous", "CUSTOM_LOCATION"); if ("on".equals(treatment) && location.isPresent()) { city = location.get().split(",")[0]; country = location.get().split(",")[1]; } else { city = "Detroit"; country = "US"; } model.addAttribute( "currentWeather", weatherService.getCurrentWeather(city, country) ); return "current-weather"; } }
Code language: Arduino (arduino)

You’ll see that we’ve made some important changes. First of all, we’ve added a new private field of type SplitClient and a constructor parameter to match.

The most important change lies in the getCurrentWeather method, though. Now it can get an optional location as a path variable. That location should consist of a city and country separated by comma. The specified city and country are passed to the WeatherControl in case the feature flag is on. Otherwise, the controller defaults to Detroit, US, as the default location.

Finally, update WeatherService to accommodate the new city and country fields. You can extract those values from the response. Replace the convert method with the following:

private CurrentWeather convert(ResponseEntity<String> response) { try { JsonNode root = objectMapper.readTree(response.getBody()); return new CurrentWeather( root.path("name").asText(), root.path("sys").path("country").asText(), root.path("weather").get(0).path("main").asText(), BigDecimal.valueOf( root.path("main").path("temp").asDouble() ), BigDecimal.valueOf( root.path("main").path("feels_like").asDouble() ), BigDecimal.valueOf( root.path("wind").path("speed").asDouble() ) ); } catch (JsonProcessingException e) { throw new RuntimeException("Error parsing JSON", e); } }
Code language: Arduino (arduino)

Here, I’ve added the two new fields for city and country for the CurrentWeather object as extracted from the OpenWeather API response.

Are we done yet? Not really. There’s one crucial step left: to activate the feature flag on Split.

Activate Your Split

Go to Split.io again. On the left panel, click on Splits and then on Create Split.

Give your split a name, and then click on Create. Then make sure the correct environment is selected and click on Add Rules:

On the next screen, click on Save Changes and then Confirm.

Add this point, commit again, and push to the GitHub repo to trigger a new build and deploy. Go to the public URL for your deployed app, and try to specify different cities after the base URL. You’ll see that, no matter what you try, the app will just show the climate for Detroit.

Go back to Split, and turn the flag on. Save and then confirm your changes. Now go back to your app, refresh the page, and you’ll see that now you can successfully specify the location.

Learn More About Continuous Deployment and Modern Feature Delivery in Java

Automation is a software developer’s bread and butter. That’s what we do: we employ automation to make all sorts of processes more efficient. But, funnily enough, it wasn’t until somewhat recently that we started leveraging automation to make the software development process itself better.

Sure, programmers have been applying automation to their own jobs since the dawn of programming time. But in recent years, developers have started exploring automation to extents we’d never done before. One way this trend manifests itself is automated testing: how it has gone from a fringe and obscure practice to a table stakes software engineering practice. The other crucial way in which automation has transformed software engineering is the combined practices of continuous integration (CI) and continuous deployment (CD).

This post was written by Carlos Schults. Carlos is a .NET software developer with experience in both desktop and web development, and he’s now trying his hand at mobile. He has a passion for writing clean and concise code, and he’s interested in practices that help you improve app health, such as code review, automated testing, and continuous build.