How to Build Microservices in Spring Boot in 15 Minutes
Tags: code, experimentation, featuredeliveryplatf, featureflags
Published: 2022-04-20 04:43:07

---
title: How to Build Microservices in Spring Boot in 15 Minutes
published: true
date: 2022-02-08 21:05:51 UTC
tags: Code,experimentation,FeatureDeliveryPlatf,featureflags
canonical_url: https://www.split.io/blog/spring-boot-microservices/
---

In this tutorial, you’re going to build a microservice in Java using Spring Boot and Spring Cloud. The service will be a simple weather service that returns the current temperature for a given zip code or city. It will consist of a public gateway API, a [Eureka discovery server](https://www.tutorialspoint.com/spring_boot/spring_boot_eureka_server.htm), and a private weather resource server. You will also use Split to implement a feature flag and see how this can be used to control application behavior at runtime. The weather resource server will retrieve current weather data from the OpenWeatherMap API.

There are a lot of parts to this project, so let’s take a closer look at them before you get started.

![](https://www.split.io/wp-content/uploads/2022/02/1.png?width=512)

The **API gateway** will be the publicly accessible service. This is the service that will act as the doorway into the microservice. In this example application, it will return processed weather data, such as temperatures for zips and city names. The weather data will be retrieved from a private service in the microservice network, which we are calling the **weather resource server**.

The API gateway will not have the URI for the private weather resource server hardcoded into it. Instead, it will look up the address on a **Eureka discovery server**. In our small, single-instance microservice there’s no actual need for a discovery server but in a larger framework, the Eureka server allows server instances to register themselves as they are added to the network and de-registered as they go down. It’s like a phone book for the microservice network (for those of us who remember phone books), or a contacts list (for anyone born after about 1985).

The discovery server has two main benefits:

1. It decouples service providers from clients without requiring DNS
2. It enables client-side load-balancing

There’s [a nice tutorial on the Spring website](https://spring.io/blog/2020/03/25/spring-tips-spring-cloud-loadbalancer) that covers load balancing with Spring Cloud and the reasons you might want to use client-side load-balancing over a traditional DNS load balancer. This tutorial won’t cover actually implementing load balancing. However, it will cover the benefits of client-side load balancing. Client-side load balancing is smarter because it has access to application state and because it can avoid the dogpiling effect that DNS caching can result in with traditional DNS load balancers.

Notice that the weather resource server will not generate the weather information itself. Instead, it will retrieve it from the OpenWeatherMap API.

The weather resource server will also implement two versions of the API, and you’ll see how to use Split’s feature flag service to dynamically enable and disable the V2 API in real-time without having to redeploy code.

You’ll also see how to implement HTTP Basic authentication on the API gateway using Spring Security 5.

## Requirements

Before you get started, you’ll need to make sure you have a few tools installed.

**Split** : Sign up for a [free Split account](https://split.io/signup/) if you don’t already have one. This is how you’ll implement the feature flags.

**HTTPie** : This is a powerful command-line HTTP request utility. Install it according to [the docs on their site](https://httpie.org/doc#installation).

**Java 11** : This project uses Java 11. OpenJDK 11 will work just as well. Instructions are found on the [OpenJDK website](https://openjdk.java.net/install/). OpenJDK can also be installed using [Homebrew](https://brew.sh/). Alternatively, [SDKMAN](https://sdkman.io/) is another excellent option for installing and managing Java versions.

**OpenWeatherMap** : for demonstration purposes in this tutorial, you’ll need a free OpenWeatherMap API account. Sign up [here](https://home.openweathermap.org/users/sign_up).

## Create the Weather Resource Server

The first component in the microservice network you’re going to create is the weather resource server. This is the private service that sits within the network that will be publicly accessed via the API gateway service. You’ll create the API gateway service in a moment.

**Step 1** – For this service, you’re going to need your OpenWeatherMap API key. Make sure you’ve already signed up for a free account. You can find the API key by signing in to your OpenWeatherMap account and going to the [API key page](https://home.openweathermap.org/api_keys) or by selecting **My API Keys** from the account drop-down.

Create a parent directory for the three Spring Boot projects your going to make, maybe something like `split-spring-cloud-microservice`.

Use the [Spring Initializr](start.spring.io/) REST API to bootstrap your project by running the following command from the project parent directory.

```
http https://start.spring.io/starter.tgz \
bootVersion==2.6.2 \
javaVersion==11 \
group==com.example.weatherservice \
artifactId==weather-service \
name==weather-service \
dependencies==actuator,cloud-eureka,web,cloud-feign,devtools,lombok \
baseDir==weather-service | tar -xzvf -
```

This creates a Spring Boot project configured to use Maven as the build system and dependency manager with Java 11 and Spring Boot version `2.6.2`. The dependencies you’re including are:

- `actuator`: exposes some pre-defined endpoints for metadata about the application
- `web`: Spring web framework that includes the MVC/non-reactive web tools necessary to create a REST API
- `cloud-eureka`: service discovery framework that the API gateway will use to look up the URI for the weather resource server
- `cloud-feign`: “declarative web service client” (as described in the Spring docs) that simplifies and structures how external web services are called
- `lombok`: helper that includes annotations for automatically generating ceremony code such as constructors, getters, and setters
- `devtools`: a Spring Boot helper for app development that includes things like live reload and automatic restart

**Step 2** – From within the `weather-service` directory, you need to create a few files and configure the application.

Open `src/main/resources/application.properties` and add the following:

```
open-weather-api-key={yourOpenWeatherApiKey}
server.port=8090
spring.application.name=weather-service
```

Make sure you replace `{yourOpenWeatherApiKey}` with your actual API key from the [OpenWeatherMap website](https://home.openweathermap.org/api_keys).

![](https://www.split.io/wp-content/uploads/2022/02/2.png?width=512)

You’re setting the server port to `8090` so that the default ports on the services do not clash.

The `spring.application.name` value will be the value that the service is registered under with the Eureka server. This value essentially takes the place of the service URI and is the value that the gateway will use to look up the weather service URI. As mentioned previously, this abstraction in the context of the microservice allows a lot of flexibility for service redundancy, load balancing, and emergency fail-over.

Update the main application file.

`com/example/weatherservice/WeatherServiceApplication.java`

```
package com.example.weatherservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class WeatherServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(WeatherServiceApplication.class, args);
    }

}
```

The only change in this file is the addition of the `@EnableFeignClients` annotation. This lets Spring Boot know that the service will use Feign clients and to look for the `@FeignClient` annotation on interfaces.

Now you need a class to hold the response from the OpenWeatherMap API. Spring Boot will use Jackson deserialization to automatically map the JSON response from the OpenWeatherMap API to the Java objects described in this file.

`com/example/weatherservice/OpenWeatherApiResponse.java`

```
package com.example.weatherservice;

import lombok.Data;

import java.util.List;

@Data
class WeatherInfo {
    private String main;
    private String description;
}

@Data
class Main {
    private Number temp;
    private Number feels_like;
    private Number pressure;
    private Number humidity;
}

@Data
class OpenWeatherApiResponse {
    private List<WeatherInfo> weather;
    private Main main;
}
```

The specific OpenWeatherMap API you’ll be calling returns current weather data. You can take a look at [the documentation for it on their website](https://openweathermap.org/current). You’ll notice that the API response actually returns a lot more data than is captured here (there just wasn’t any reason to try and deserialize and map all of the data from the returned JSON).

**Step 3** – Create a file to hold the V1 version of the weather resource server.

`com/example/weatherservice/WeatherRestControllerV1.java`

```
package com.example.weatherservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

@FeignClient(name="weather-client-v1", url="http://api.openweathermap.org/")
interface OpenWeatherClientV1 {
    @RequestMapping(method = RequestMethod.GET, value = "data/2.5/weather?units=imperial&zip={zip}&appid={apiKey}", consumes = "application/json")
    OpenWeatherApiResponse getWeatherbyZip(@RequestParam("zip") String zip, @RequestParam("apiKey") String apiKey);
}

@RestController()
@RequestMapping("api/v1")
public class WeatherRestControllerV1 {

    @Value( "${open-weather-api-key}" )
    private String apiKey;

    Logger logger = LoggerFactory.getLogger(WeatherRestControllerV1.class);

    private final OpenWeatherClientV1 weatherClient;

    public WeatherRestControllerV1(OpenWeatherClientV1 weatherClient) {
        this.weatherClient = weatherClient;
    }

    @GetMapping("/weather")
    OpenWeatherApiResponse getWeatherByZip(@RequestParam("zip") String zip) {
        logger.info("Getting weather for zip = " + zip);
        OpenWeatherApiResponse weatherApiResponse = weatherClient.getWeatherbyZip(zip, apiKey);
        return weatherApiResponse;
    }

}
```

The interface at the top of this file is the Feign client that describes to Spring Boot how you want to make calls to the OpenWeatherMap API. That client is then injected into the `WeatherRestControllerV1`, where it is used to retrieve weather information that is exposed by its `getWeatherByZip(zip)`, which is what the API gateway will call.

The version 1 API has only one method that returns the weather by zip, which is mapped to `api/v1/weather` and expects the `zip` as a query param.

**Step 4** – Create a controller for the V2 version of the weather resource server.

`com/example/weatherservice/WeatherRestControllerV2.java`

```
package com.example.weatherservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

@FeignClient(name="weather-client-v2", url="http://api.openweathermap.org/")
interface OpenWeatherClientV2 {
    @RequestMapping(method = RequestMethod.GET, value = "data/2.5/weather?units=imperial&zip={zip}&appid={apiKey}", consumes = "application/json")
    OpenWeatherApiResponse getWeatherByZip(@RequestParam("zip") String zip, @RequestParam("apiKey") String apiKey);

    @RequestMapping(method = RequestMethod.GET, value = "data/2.5/weather?units=imperial&q={cityStateCountry}&appid={apiKey}", consumes = "application/json")
    OpenWeatherApiResponse getWeatherByCity(@RequestParam("cityStateCountry") String cityStateCountry, @RequestParam("apiKey") String apiKey);
}

@RestController("v2")
@RequestMapping("api/v2")
public class WeatherRestControllerV2 {

    @Value( "${open-weather-api-key}" )
    private String apiKey;

    Logger logger = LoggerFactory.getLogger(WeatherRestControllerV2.class);

    private final OpenWeatherClientV2 weatherClient;

    public WeatherRestControllerV2(OpenWeatherClientV2 weatherClient) {
        this.weatherClient = weatherClient;
    }

    @GetMapping("/weather")
    OpenWeatherApiResponse getWeather(@RequestParam(required = false) String zip, @RequestParam(required = false) String cityStateCountry) {
        if (zip != null) {
            logger.info("Getting weather for zip = " + zip);
            OpenWeatherApiResponse weatherApiResponse = weatherClient.getWeatherByZip(zip, apiKey);
            return weatherApiResponse;
        }
        else if (cityStateCountry != null) {
            logger.info("Getting weather for cityStateCountry = " + cityStateCountry);
            OpenWeatherApiResponse weatherApiResponse = weatherClient.getWeatherByCity(cityStateCountry, apiKey);
            return weatherApiResponse;
        }
        else {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Must specify either `zip` or `cityStateCountry`");
        }
    }

}
```

The V2 API adds the ability to retrieve weather information based on city, state, and country. Notice that the private, weather resource server will simultaneously expose both API versions. When you implement the gateway, you will use feature flags and Split to control access to the V2 API, simulating a test rollout.

## Create the API Gateway and the Eureka Server

**Step 5** – Again use Spring Initializr to download a pre-configured starter. Run the command below from a Bash shell within the parent directory of the tutorial project (not within the weather resource server project directory).

```
http https://start.spring.io/starter.tgz \
bootVersion==2.6.2 \
javaVersion==11 \
group==com.example.gateway \
artifactId==api-gateway \
name==api-gateway \
dependencies==cloud-eureka,web,cloud-feign,devtools,lombok \
baseDir==api-gateway | tar -xzvf -
```

The dependencies and other parameters here are very similar, so I won’t explain them.

Add the following line to the `application.properties` file.

`src/main/resources/application.properties`

```
spring.application.name=api-gateway
```

Update the `ApiGatewayApplication` class to the following:

`src/main/java/com/example/apigateway/ApiGatewayApplication.java`

```
package com.example.apigateway;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

@FeignClient(name="weather-service", path = "api/v1", contextId = "v1")
interface WeatherServerClientV1 {
    @RequestMapping(method = RequestMethod.GET, value = "weather", consumes = "application/json")
    OpenWeatherApiResponse getWeatherByZip(@RequestParam("zip") String zip);
}

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

    @RestController("gateway-api")
    @RequestMapping()
    public class WeatherGatewayService {

        Logger logger = LoggerFactory.getLogger(WeatherGatewayService.class);

        private final WeatherServerClientV1 weatherClientV1;

        public WeatherGatewayService(WeatherServerClientV1 weatherClientV1) {
            this.weatherClientV1 = weatherClientV1;
        }

        @GetMapping("/temperature/zip/{zip}")
        String getTempByZip(@PathVariable("zip") String zip) {
            logger.info("Getting weather for zip = " + zip);
            OpenWeatherApiResponse weatherApiResponse = weatherClientV1.getWeatherByZip(zip);
            logger.info(weatherApiResponse.toString());
            return weatherApiResponse.getMain().getTemp().toString()+"°F, feels like " + weatherApiResponse.getMain().getFeels_like().toString()+"°F";
        }

    }

}
```

This file has the Feign client interface for accessing the V1 weather resource server as well as a public method accessible at the endpoint `/temperature/zip/{zip}`. This does not yet have any V2 methods or endpoints. In the next section, you’ll see how to add the V2 API methods and do a controlled rollout with Split and feature flags.

**Step 6** – Create the data model file for the weather data.

`src/main/java/com/example/apigateway/OpenWeatherApiResponse.java`

```
package com.example.apigateway;

import lombok.Data;

import java.util.List;

@Data
class WeatherInfo {
    private String main;
    private String description;
}

@Data
class Main {
    private Number temp;
    private Number feels_like;
    private Number pressure;
    private Number humidity;
}

@Data
class OpenWeatherApiResponse {
    private List<WeatherInfo> weather;
    private Main main;
}
```

This is just a duplicate of the same file in the weather resource server.

Finally, the last piece, you need to create the Eureka discovery server. Again, from the parent directory, run the following command to download a starter project.

```
http https://start.spring.io/starter.tgz \
bootVersion==2.6.2 \
javaVersion==11 \
group==com.example.discovery \
artifactId==discovery-service \
name==discovery-service \
dependencies==cloud-eureka-server \
baseDir==discovery-service | tar -xzvf -
```

The only dependency here is `cloud-eureka-server`, which brings in the necessary dependencies to creating a discovery service.

Update `application.properties` to the following.

`src/main/resources/application.properties`

```
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF
```

Open `DiscoveryServiceApplication` and make sure it matches the following. All you are doing is adding the `@EnableEurekaServer` annotation.

```
package com.example.discoveryservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class DiscoveryServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(DiscoveryServiceApplication.class, args); }

}
```

That’s all you have to do to create the Eureka discovery server!

## Test the Weather App Microservice

**Step 7** – Start the projects using `./mvnw spring-boot:run` from a Bash shell. Start the Eureka server first, then the private weather resource server, and finally the API gateway. If you start things in the wrong order, the dead will definitely rise from their graves and take over the world, so don’t mix it up.

Once all three services are running, open the Spring Eureka dashboard at http://localhost:8761/.

You should see something like this, with two service instances running.

![](https://www.split.io/wp-content/uploads/2022/02/3.png?width=512)

The ports for the different services you have running are:

- 8080: the public weather service port, the API gateway
- 8090: the private weather resource server within the microservice network
- 8761: the Eureka discovery server port

You can test the microservice by making the following request from a new Bash shell.

```
http :8080/temperature/zip/78025
```

```
HTTP/1.1 200 
...

82.06°F, feels like 82.4°F`
```

You could also make a request to the private weather resource service. This is the JSON data that the private API is returning from the OpenWeatherMaps service.

```
http :8090/api/v1/weather?zip=78025
```

```
HTTP/1.1 200 
...

{
    "main": {
        "feels_like": 81.82,
        "humidity": 47,
        "pressure": 1011,
        "temp": 81.48
    },
    "weather": [
        {
            "description": "clear sky",
            "main": "Clear"
        }
    ]
}
```

This corresponds to the following request being made to the OpenWeatherMaps API.

```
http api.openweathermap.org/data/2.5/weather zip==78025 appid=={apiKey}
```

```
HTTP/1.1 200 OK
...

{
    "base": "stations",
    "clouds": {
        "all": 3
    },
    "cod": 200,
    "coord": {
        "lat": 30.0731,
        "lon": -99.269
    },
    "dt": 1640472064,
    "id": 0,
    "main": {
        "feels_like": 300.66,
        "grnd_level": 952,
        "humidity": 47,
        "pressure": 1011,
        "sea_level": 1011,
        "temp": 300.45,
        "temp_max": 303.96,
        "temp_min": 296.45
    },
    "name": "Ingram",
    "sys": {
        "country": "US",
        "id": 2003197,
        "sunrise": 1640439056,
        "sunset": 1640475813,
        "type": 2
    },
    "timezone": -21600,
    "visibility": 10000,
    "weather": [
        {
            "description": "clear sky",
            "icon": "01d",
            "id": 800,
            "main": "Clear"
        }
    ],
    "wind": {
        "deg": 278,
        "gust": 2.67,
        "speed": 2.54
    }
}
```

## Use Split And Feature Flags to Implement a Controlled Rollout

**Step 8** – Now you’re going to use Split’s implementation of feature flags. Feature flags are a way to control code dynamically at runtime. You can think of them as dynamic variables whose state can be controlled in real-time, both manually and automatically, based on a large number of configurable parameters on the Split dashboard. In this example, you’ll use a feature flag to simulate a controlled roll-out of the V2 API, but there are lots of potential uses cases for feature flags.

They’re applicable any time you want to segment application behavior based on a user identity or any other application parameter. You could, for example, use it to give some users access to premium features, to selectively test features with certain users, or to expose admin and debug features only to certain accounts.

Make sure you’ve already signed up for a [free Split developer account](https://split.io/signup/).

Before you get started, let’s go over a little terminology and some basic concepts that Split uses. A feature flag is also called a **split** , in that it is a decision point in the code. The split is a String key that lives on the Split servers whose value can be looked up using the Split SDK. As you’ll see, Split does some fancy caching so that you don’t have to worry about network calls slowing down your code when this lookup happens.

The value a split takes is a **treatment**. Treatments are strings. Splits can have any number of treatments from two to dozens. The treatment, or value, that a split has at any given point is determined by rules configured on the Split dashboard. These rules are called **targeting rules**. There are a lot of options, and changes made to a Split are propagated in real-time to your application. Thus they are like having dynamic, real-time, configurable switched in your application code.

One common way of determining a split’s treatment is by using a **segment** of the user population. A segment is a sub-group of your users. This could be your testers. This could be your freemium users. This could be the newest users. This could be everyone in Austin, Texas. As you’ll see, there’s a ton of flexibility in how the system is implemented. But the idea is, for example, that you can create groups of users, or segments, for whom the split has one treatment while the rest default to another treatment.

In the next section of the tutorial, you’re going to see how you can use Split to selectively expose the V2 API, which you’ll implement below, to a segment of users. You’ll see how this can be done in real-time, without redeploying any code, and how you can either roll the deployment back or expand the deployment, also dynamically without code changes, depending on how the rollout goes.

## Create a Split Feature Flag

**Step 9** – Log into [your Split dashboard](https://app.split.io/login).

From the left-menu, under **TARGET** , click **Splits.** Click the blue **Create split** button.

![](https://www.split.io/wp-content/uploads/2022/02/4.png?width=399)

Name the split `v2-deployment`. Change the **traffic type** to `user`.

Click **Create**.

You just created the split (or feature flag). Now you need to define the different treatments (or values) the split can take and some targeting rules to determine under what conditions each state is active.

In this section of the tutorial, you’re going to use two users to simulate a test deployment. `user1` will have the new V2 API activated while `user2` will only see the old V1 API. While here you are only using two users, it’s very simple to expand this to a list of users (a segment) that is either imported via a CSV file or dynamically assigned based on user attributes.

On the `v2-deployment` split panel, click the **Add Rules** button.

![](https://www.split.io/wp-content/uploads/2022/02/5.png?width=512)

This creates a new set of targeting rules for your split. It will open the targeting rules panel. Each environment has a different set of targeting rules. The environment defaults to `Staging-Default`, which is a test environment. There is also a production environment called `Prod-Default` pre-configured. Environments can be added and configured in the **Environments** section of the main menu (on the left of the dash).

![](https://www.split.io/wp-content/uploads/2022/02/6.png?width=512)

The first header, **Define treatments** , is where the treatments are defined. The default treatment simulates a boolean on/off system. Remember, though, that these are just string values and can be given any name and can take on any arbitrary meaning in your application. Further, you can have more than two treatments per split.

**Step 10** – Change the `on` treatment to `v2` and the `off` treatment to `v1.

![](https://www.split.io/wp-content/uploads/2022/02/7.png?width=512)

Defining it in this way means that the default rule and the default treatment will be assigned such that `v1` is the default. This is what we would want in our test deployment situation. If you scroll down you’ll see that **Set the default rule** and **Set the default treatment** both have `v1` as their value.

These two defaults are important to understand. The default rule is the treatment that will be served if none of the targeting rules apply. It’s the default value if the treatment request from the Split client works properly, but no case is defined in the targeting rules that applies to the user. The default treatment is what will be served if, for example, the Split client cannot reach the servers or some other fault occurs. It will also be served if the split is killed or the customers are excluded from the split.

Now, you need to define a targeting rule that serves the `v2` treatment for the `user1` user.

Under **Set targeting rules** , click **Add rule**.

You want the rule to read: `If user is in list 'user1', serve 'v2'`

Click the drop-down and select **string** and **is in list**. Add `user1` to the text box to the right of the drop-down. Select `v2` in the **serve** drop-down.

![](https://www.split.io/wp-content/uploads/2022/02/8.png?width=512)

Click **Save changes** at the top of the panel.

Click **Confirm**.

Your split is configured and active. Now you need to add the Split SDK to the application and add the code that will expose the V2 API using the split. Before you do that, however, you’re going to add some simple authentication to the application to allow for different users.

## Add Spring Security 5 Basic Auth to App

**Step 11** – In order to have the users to work with, you’re going to add a very simple auth scheme to the API gateway application using Spring Security 5 configured for HTTP basic auth with an in-memory user store.

Open the API gateway project.

Add the Spring Security dependency to the `pom.xml` file between the `<dependencies></dependencies>` tags.

```
<dependencies> 
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    ...
</dependencies>
```

Create a `SecurityConfiguration` class and add the following contents:

`src/main/java/com/example/apigateway/SecurityConfiguration.java`

```
package com.example.apigateway;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user1").password("{noop}user1").roles("USER")
                .and()
                .withUser("user2").password("{noop}user2").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic() 
                .and()
                .authorizeRequests()
                .anyRequest().authenticated();
    }

}
```

This configures Spring Boot to authorize all requests on the resource server and to use HTTP Basic. It also adds our hard-coded, in-memory users: `user1` and `user2`. Hopefully needless to say, this auth scheme is not ready for production and is for the purposes of this tutorial. However, Spring Security is easily configured for use with OAuth 2.0 and OIDC providers, so adapting this to a live scenario would not be that difficult (but that’s a topic for a different tutorial).

## Implement the V2 API and the Split

**Step 12** – Still in the API gateway project, add the Split SDK dependency to the `pom.xml` file between the `<dependencies></dependencies>` tags.

```
<dependencies> 
    ...
    <dependency>
        <groupId>io.split.client</groupId>
        <artifactId>java-client</artifactId>
        <version>4.2.1</version>
    </dependency>
    ...
</dependencies>
```

Create a new class for to configure the Split client bean that you’ll inject into the controllers.

`src/main/java/com/example/apigateway/SplitConfiguration.java`

```
package com.example.apigateway;

import io.split.client.SplitClient;
import io.split.client.SplitClientConfig;
import io.split.client.SplitFactory;
import io.split.client.SplitFactoryBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SplitConfiguration {

    @Value("#{ @environment['split.api.key'] }")
    private String apiKey;

    @Bean
    public SplitClient splitClient() throws Exception {
        SplitClientConfig config = SplitClientConfig.builder()
                .setBlockUntilReadyTimeout(20000)
                .enableDebug()
                .build();

        SplitFactory splitFactory = SplitFactoryBuilder.build(apiKey, config);
        SplitClient client = splitFactory.client();
        client.blockUntilReady();

        return client;
    }
}
```

This code is more-or-less straight out of [the Split Java SDK docs.](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK). You’re pulling your Split API key and building a client bean, setting some default values.

**Step 13** – You need to add your Split API key to your `application.properties` file. The API keys need to match the environment that you defined your split in. The environment should be `Staging-Default` if you followed this tutorial.

![](https://www.split.io/wp-content/uploads/2022/02/9.png?width=512)

To find the API keys, click on the **DE** icon at the top of the left menu. Click **Admin settings**. Select **API keys**.

You want the `Staging-Default` and `Server-side` keys. Java is a server-side language. Different keys are used for client-side Javascript apps because those keys are essentially public.

![](https://www.split.io/wp-content/uploads/2022/02/10.png?width=512)

**Step 14** – Click **copy** to copy the keys to your clipboard.

In the API gateway properties file, add the following line, replacing `{yourSplitApiKey}` with your actual API key.

```
split.api.key={yourSplitApiKey}
```

Replace the contents of the main `ApiGatewayApplication` class with the following:

`src/main/java/com/example/apigateway/ApiGatewayApplication.java`

```
package com.example.apigateway;

import io.split.client.SplitClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.server.ResponseStatusException;

import java.security.Principal;

// V1 Feign Client - used to access version 1 API on the weather resource server
@FeignClient(name="weather-service", path = "api/v1", contextId = "v1")
interface WeatherServerClientV1 {
    @RequestMapping(method = RequestMethod.GET, value = "weather", consumes = "application/json")
    OpenWeatherApiResponse getWeatherByZip(@RequestParam("zip") String zip);
}

// V2 Feign Client - used to access version 2 API on the weather resource server
@FeignClient(name="weather-service", path = "api/v2", contextId = "v2")
interface WeatherServerClientV2 {
    @RequestMapping(method = RequestMethod.GET, value = "weather", consumes = "application/json")
    OpenWeatherApiResponse getWeatherByZip(@RequestParam("zip") String zip);

    @RequestMapping(method = RequestMethod.GET, value = "weather", consumes = "application/json")
    OpenWeatherApiResponse getWeatherByCityStateCountry(@RequestParam("cityStateCountry") String cityStateCountry);
}

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

    @RestController("gateway-api")
    @RequestMapping()
    public class WeatherGatewayService {

        Logger logger = LoggerFactory.getLogger(WeatherGatewayService.class);

        private final WeatherServerClientV1 weatherClientV1;
        private final WeatherServerClientV2 weatherClientV2;
        // the Split Client bean configured in SplitConfiguration.java
        private final SplitClient splitClient;

        public WeatherGatewayService(WeatherServerClientV1 weatherClientV1, WeatherServerClientV2 weatherClientV2, SplitClient splitClient) {
            this.weatherClientV1 = weatherClientV1;
            this.weatherClientV2 = weatherClientV2;
            this.splitClient = splitClient;
        }

        // Retrieves the username from the authenticated principal and
        // passes this username to the splitClient.getTreatment() method,
        // which is where the treatment is retrieved from Split (it's
        // actually cached so that this call is really fast but still
        // updated in near real-time)
        String getTreatmentForPrincipal(Principal principal) {
            String username = principal.getName();
            logger.info("username = " + username);
            String treatment = splitClient.getTreatment(username, "v2-deployment");
            logger.info("treatment = " + treatment);
            return treatment;
        }

        @GetMapping("/temperature/zip/{zip}")
        String getTempByZip(@PathVariable("zip") String zip, Principal principal) {
            String treatment = getTreatmentForPrincipal(principal);
            logger.info("Getting weather for zip = " + zip);

            OpenWeatherApiResponse weatherApiResponse = null;

            // using the treatment to select the correct API version
            if (treatment.equals("v1")) {
                weatherApiResponse = weatherClientV1.getWeatherByZip(zip);
            }
            else if (treatment.equals("v2")){
                weatherApiResponse = weatherClientV2.getWeatherByZip(zip);
            }
            else {
                throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
            }

            logger.info(weatherApiResponse.toString());
            return weatherApiResponse.getMain().getTemp().toString()+"°F, feels like " + weatherApiResponse.getMain().getFeels_like().toString()+"°F";
        }

        @GetMapping("/temperature/citystatecountry")
        String getTempByCityStateCountry(
                @RequestParam() String city,
                @RequestParam(required = false) String state,
                @RequestParam(required = false) String country,
                Principal principal) {
            String treatment = getTreatmentForPrincipal(principal);
            // only the users with treatment "v2" should be able to access this method
            if (treatment.equals("v2")) {
                logger.info("Getting weather for city = " + city + ", state = " + state + ", country = " + country);
                OpenWeatherApiResponse weatherApiResponse = weatherClientV2.getWeatherByCityStateCountry(city+","+state+","+country);
                logger.info(weatherApiResponse.toString());
                return weatherApiResponse.getMain().getTemp().toString()+"°F, feels like " + weatherApiResponse.getMain().getFeels_like().toString()+"°F";
            }
            else if (treatment.equals("v1")) {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Method not found");
            }
            else {
                throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }

        @GetMapping("/weather/zip/{zip}")
        OpenWeatherApiResponse getWeatherByZip(@PathVariable("zip") String zip, Principal principal) {
            String treatment = getTreatmentForPrincipal(principal);
            // only the users with treatment "v2" should be able to access this method
            if (treatment.equals("v2")) {
                OpenWeatherApiResponse weatherApiResponse = weatherClientV2.getWeatherByZip(zip);
                return weatherApiResponse;
            }
            else if (treatment.equals("v1")) {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Method not found");
            }
            else {
                throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }

    }

}
```

**Step 15** – There’s a lot going on in this file, so I’ll point some things out. At the top of the file, you now have two Feign clients: one for the V1 API and one for the V2 API. Remember that these Feign clients are what structure the calls to the private weather resource service within the simple microservice (whose actual URI is discovered by the Eureka discovery server based on the `name` attribute). Notice that I had to set a `contextId` on these to allow using the same name for both. This name maps to the service name that is used by the Eureka gateway (defined by the `spring.application.name` property in the weather resource server `application.properties` file).

If you look at the constructor for the `WeatherGatewayService` inner class, you’ll see that the Split client is being injected into the constructor along with the two Feign clients.

The `splitClient` is used in the `getTreatmentForPrincipal(Principal)` method. This method is called by each of the controller methods. This is where the authenticated username is used to retrieve the treatment for the split. This happens in the `splitClient.getTreatment(username, treatment name)`. You can imagine that the split client is making a request to the Split servers here and looking up the correct value. However, that could potentially be very slow. The split client does some nice behind-the-scenes caching and polling. For that reason, it doesn’t really need to make a network call to return the treatment; and as such, the `getTreatment()` method returns almost instantly. Treatment values are still updated within seconds of being changed on the Split dashboard.

If you look at the endpoints, you’ll see that the first endpoint (`/temperature/zip/{zip}`) has both a V1 and a V2 definition. The treatment value is used to select the correct Feign client. The two new methods (the second and third endpoints, `/temperature/citystatecountry` and `/weather/zip/{zip}`) only have a V2 definition. As such, they throw a `Method not found` exception if a user with the V1 treatment tries to access them. On all three endpoints, an internal service error is thrown if any other treatment value is returned. This may not be the desired behavior in a production scenario. Maybe instead you want to fallback to the V1 value,but that’s up to you.

Stop the API gateway project with `control-c` and restart it.

```
./mvnw spring-boot:run
```

Make sure the Eureka discovery service and the weather resource service are also still running.

Use HTTPie to make some requests.

Both `user1` and `user2` should be able to make requests on the original endpoint. You’re using HTTPie to pass the the basic auth credentials (`-a user1:user2`);

```
$: http -a user1:user1 :8080/temperature/zip/78025 
HTTP/1.1 200 
...

62.58°F, feels like 60.84°F
```

```
$: http -a user2:user2 :8080/temperature/zip/78025 
HTTP/1.1 200 
...

62.58°F, feels like 60.84°F
```

You can verify that an unauthenticated request will fail.

```
$: http :8080/temperature/zip/78025 
HTTP/1.1 401 
...
```

According to our treatment, `user1` should be able to access the V2 endpoints while `user2` should not.

```
$: http -a user1:user1 :8080/weather/zip/78025 
...

{
    "main": {
        "feels_like": 60.84,
        "humidity": 49,
        "pressure": 1013,
        "temp": 62.58
    },
    "weather": [
        {
            "description": "broken clouds",
            "main": "Clouds"
        }
    ]
}


```
$: http -a user2:user2 :8080/weather/zip/78025 
HTTP/1.1 404
```

If you look at the console output for these requests, you’ll see that `user1` is mapping to the `v2` treatment and `user2` is mapping to the `v1` treatment, exactly as expected.

```
2021-12-26 11:40:32.384 INFO 162549 --- [ssionsManager-0] i.s.c.i.ImpressionsManagerImpl : Posting 1 Split impressions took 354 millis
2021-12-26 11:41:17.077 INFO 162549 --- [nio-8080-exec-3] GatewayApplication$WeatherGatewayService : username = user1
2021-12-26 11:41:17.077 INFO 162549 --- [nio-8080-exec-3] GatewayApplication$WeatherGatewayService : treatment = v2
2021-12-26 11:41:20.397 INFO 162549 --- [nio-8080-exec-5] GatewayApplication$WeatherGatewayService : username = user2
2021-12-26 11:41:20.398 INFO 162549 --- [nio-8080-exec-5] GatewayApplication$WeatherGatewayService : treatment = v1
2021-12-26 11:41:20.398 WARN 162549 --- [nio-8080-exec-5] .w.s.m.a.ResponseStatusExceptionResolver : Resolved [org.springframework.web.server.ResponseStatusException: 404 NOT_FOUND "Method not found"]
```

Now, imagine that the V2 deployment performs flawlessly for a few weeks and you’re ready to enable it for all users. That’s easy to do.

Open your Split dashboard. Go to **TARGET** and select **Splits**. Select the `v2-deployment` split. There are a few ways you could do this. You could add `user1` to the list of white-listed users in the targeting rule. However, since you now want V2 to become the standard for all users, you might as well just make it the default rule and default treatment.

Under both **Set the default rule** and **Set the default treatment** , select `v2`.

At the top of the panel, click **Save changes**. Click **Confirm**.

Once you click confirm, your changes are live.

Try making a request on the V2 API with `user2`.

```
$: http -a user2:user2 :8080/weather/zip/78025 
HTTP/1.1 200 
...

{
    "main": {
        "feels_like": 60.89,
        "humidity": 50,
        "pressure": 1012,
        "temp": 62.58
    },
    "weather": [
        {
            "description": "broken clouds",
            "main": "Clouds"
        }
    ]
}
```

This request just previously failed with a 404 for `user2`, but because now all users have been authorized to use the V2 API, it works. This was done without changing any code or making any deployments.

You could easily do the opposite, if you wanted. Imagine that the deployment didn’t work (this, of course, would never happen to _you_, but just for the sake of completeness). In that case, you would want to instead have all users hit the V1 API. To do that, you would delete the targeting rule (under **Set targeting rules** ) and change both the **Set the default rule** and **Set the default treatment** back to `V1`. **Save changes** and **Confirm**.

You’re back to getting a `404` for `user2` for the V2 API endpoints, but not even `user1` will get an error as well.

```
$: http -a user2:user2 :8080/weather/zip/78025 
HTTP/1.1 404 
...
```

```
$: http -a user1:user1 :8080/weather/zip/78025 
HTTP/1.1 404 
...
```

Both users, however, can still access the V1 endpoint.

```
$: http -a user1:user1 :8080/temperature/zip/78025 
HTTP/1.1 200 
...

66.18°F, feels like 64.71°F
```

```
$: http -a user1:user1 :8080/temperature/zip/78025 
HTTP/1.1 200 
...

66.18°F, feels like 64.71°F
```

## Split Data Logging

**Step 16** – The last thing I want to demonstrate is Split’s ability to track events logged through the Split client. The Split client has a `track()` method that can send arbitrary event data to the Split servers where it is logged. You can read more about this in [the Split SDK docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK#track). This is a deep feature that includes the ability to calculate metrics based on tracked events and to trigger alerts. This tutorial won’t go too deep into this ability, except to quickly demonstrate the `track()` method and introduce you to the API.

Open the `ApiGatewayApplication.java` file and add the following line to the `getWeatherByZip()` method as shown below.

```
splitClient.track(principal.getName(), "user", "json-access");
```


```
@GetMapping("/weather/zip/{zip}")
OpenWeatherApiResponse getWeatherByZip(@PathVariable("zip") String zip, Principal principal) {
    String treatment = getTreatmentForPrincipal(principal);
    // only the users with treatment "v2" should be able to access this method
    if (treatment.equals("v2")) {
        ///////////////////////////////////////////////////////////////////////////
        splitClient.track(principal.getName(), "user", "json-access", zip); // <- ADD ME
        ///////////////////////////////////////////////////////////////////////////
        OpenWeatherApiResponse weatherApiResponse = weatherClientV2.getWeatherByZip(zip);
        return weatherApiResponse;
    }
    else if (treatment.equals("v1")) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Method not found");
    }
    else {
        throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
```

This will log an event with Split every time a user uses the V2 API to access the JSON data. Useful, perhaps, if you wanted to know how many users were using the data API endpoint added in the V2 app.

The basic signature of the `track()` method is as follows.

```
boolean trackEvent = client.track("key", "TRAFFIC_TYPE", "EVENT_TYPE", VALUE);
```

The method you’re adding above does not log a value. However, numerical values (a Java `double`) can be logged with each event. You could, for example, track load times or method performance to see if a new release has slowed user access times.

**Step 17** – To see this in action, open the Split dashboard. Click on **Data hub** from the main menu. Make sure the `Staging-Default` environment is selected. On the **Data type** drop-down, select `Events`. Click the blue **Query** button. Keep this browser tab open.

This allows you to monitor in real-time the entries created by the `splitClient.track()` method calls.

Use `control-c` to stop and restart your API gateway app with `./mvnw spring-boot:run`.

Make a few calls to the tracked method.

```
http -a user1:user1 :8080/weather/zip/78025;
http -a user1:user1 :8080/weather/zip/97229;
http -a user1:user1 :8080/weather/zip/77001;
```

It may take a few seconds, but you’ll see the `track()` calls logged.

![](https://www.split.io/wp-content/uploads/2022/02/11.png?width=512)

I won’t demonstrate it here, but if you change the **Data type** to `Impressions`, you can track the `splitClient.getTreatment()` calls as well. Watching all of this come in real-time is fun, but the real power is in the metrics. To learn more about Split event monitoring and metrics, dig into [the Split docs](https://help.split.io/hc/en-us/categories/360001538172-Monitor-Experiment).

## Learn More About Spring Boot

In this tutorial, you covered a lot of ground. You saw how to use Spring Cloud to create a scalable, dynamic microservice network that uses a Eureka discovery server to register and lookup services; that implements a private web service that calls outside APIs and re-packages the data for downstream services; and that has a public API gateway that exposes the service to external clients.

You saw how to use Feign to create structured HTTP clients. You also saw how to implement HTTP Basic auth to secure the app using Spring Security 5, and how Split’s implementation of feature flags allows for application behavior to be dynamically changed at runtime. Finally, you had a quick introduction to Split’s event tracking API.

Ready to learn more? We’ve got some additional resources covering all of these topics and more! Check out:

[Containerization with Spring Boot and Docker](https://www.split.io/blog/containerization-spring-boot-docker/)  
[A Simple Guide to Reactive Java with Spring Webflux](https://www.split.io/blog/reactive-java-spring-webflux/)  
[Get Started with Spring Boot and Vue.js](https://www.split.io/blog/vue-js-spring-boot-tutorial/)

To stay up to date on all things in feature flagging and app building, follow us on [Twitter](https://twitter.com/splitsoftware) @splitsoftware, and subscribe to our [YouTube channel](https://www.youtube.com/c/SplitSoftware)!