Testing in Production with Spring Boot with Kotlin
Tags: tutorial, kotlin, testing
Published: 2021-10-28 22:41:51

---
title: Testing in Production with Spring Boot with Kotlin
published: true
date: 2021-10-25 21:47:30 UTC
tags: tutorial, kotlin, testing
canonical_url: https://www.split.io/blog/test-production-spring-boot-kotlin/

---

In this tutorial, you are going to build a Spring Boot REST application using Kotlin. Your resource server will use Spring Data to map a Kotlin class to a database table using simple JPA annotations. You will also use Spring Security to add HTTP Basic authentication and method-level authorization to the HTTP endpoints. Your Spring MVC-powered resource server will allow for the creation, deletion, update, and listing of data object instances. Finally, you will use [Split’s Java SDK](https://www.split.io/product/languages/java-sdk/) to implement a feature flag that will allow you to dynamically update app behavior based on user attributes.

You may at this point be asking a few questions. Why Kotlin? I thought Spring was Java. What is Kotlin and why use it? Or, perhaps, what is a feature flag? Why would I want to use a feature flag in my code?

Kotlin is a JVM-based language built by JetBrains (the people who make IntelliJ). Java compiles down to an intermediate bytecode that runs on the Java Virtual Machine (JVM). This is what allows Java to be platform interdependent while still retaining a lot of the performance benefits of compiled code. Kotlin, which you can think of as Java re-imagined, also compiles down to the same byte-code. This means that Kotlin and Java are totally interoperable. Kotlin classes can depend on and use Java libraries. You can mix and match Java and Kotlin classes in the same project if you want.

So why bother with Kotlin at all? Java is a great language, but it has a tendency to be long-winded with lots of ceremony code. Further, the Java syntax was developed before the functional coding paradigm emerged, and Java can only change so much because of all the institutional success it has had.

The last decade has seen an amazing amount of growth in programming language development, from modern Javascript to more niche languages like Go and Scala. Kotlin is an outgrowth of this development. It merges a lot of functional coding ability and syntactic simplicity of scripting languages with a Java-esque syntax to create a concise, powerful coding language. It also adds some incredibly useful features such as built-in null-pointer checking syntax.

Now, what about feature flags? Feature flags are a way of controlling application behavior dynamically, at run-time. They are flags, or variables, that can be used in your application code whose value can be updated in real-time. Split provides a powerful, software-as-service implementation of a feature flag system.

Feature flags can be used for testing, to carefully introduce a new feature and roll it back if it doesn’t work, or expand its deployment if it does — all without having to recompile and redeploy. Feature flags can also be used to segment features based on things like location or subscription level. There are endless possible uses for feature flags. Ultimately, one of the main uses is to decouple changes in code behavior from the need to deploy code. Split also has an [extensive metrics and event monitoring API](https://www.split.io/blog/extending-monitoring-from-application-performance-to-features/) that can be used to track application use and customer behavior.

In this tutorial, you will be building and serving imaginary paper airplane designs. Each paper airplane design will have a number of folds it takes to make the paper airplane as well as a name. Further, the paper airplane will have a boolean value named `isTesting` that will be the flag you will use to demonstrate the feature flags. You will be able to “serve” paper airplane specifications via the REST interface, and the server will be able to create, read, update, and delete paper airplane designs from the database.

## Dependencies

**Java** : This tutorial uses Java 11. You can download and install Java by going to [the AdaptOpenJdk website](https://adoptopenjdk.net/installation.html#). Or you can use a version manager like [SDKMAN](https://sdkman.io/) or even [Homebrew](https://brew.sh/).

**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 you’ll use to test the reactive server. Install it according to [the docs on their site](https://httpie.org/doc#installation).

## Bootstrap the App with Spring Initializr

You will use the Spring Initializr project to download a pre-configured starter project. Open [this link](https://start.spring.io/#!type=gradle-project&language=kotlin&platformVersion=2.5.5&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=web,data-jpa,h2,security) and click the **Generate** button. Download the `demo.zip` file and unzip it somewhere on your local computer.

![1](https://www.split.io/wp-content/uploads/2021/10/1-2-1016x1024.png?width=1016)

You’re downloading a Spring Boot project that is configured to use Gradle as the build system, Kotlin as the main project language, and Java version 11.

There are four dependencies included as well:

- **Spring Web** : adds basic HTML and REST capabilities.
- **Spring Data JPA** : allows the app to map class objects to database tables using annotations.
- **H2 Database** : provides a simple, in-memory database that is perfect for demo projects and testing.
- **Spring Security** : includes Spring security functions that will allow the app to include HTTP Basic authentication.

## Create a Secure CRUD App

The bootstrapped app you just downloaded doesn’t do much yet. It’s just the project skeleton. The first step is to implement a CRUD (Create, Read, Update, and Delete) application. You’ll do this entirely in Kotlin. As mentioned, the application will be secured using Spring Security and HTTP Basic.

Replace the contents of the `DemoApplication.kt` file with the code below.

`src/main/kotlin/com/example/demo/DemoApplication.kt`

```kotlin
package com.example.demo

import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

@SpringBootApplication
class DemoApplication {

    // Initialize some sample data
    @Bean
    fun init(repository: PaperAirplaneRepository): ApplicationRunner {
        return ApplicationRunner { _: ApplicationArguments? ->
            var paperAirplane = PaperAirplane(null, "Bi-Fold", 2, false);
            repository.save(paperAirplane);
            paperAirplane = PaperAirplane(null, "Tri-Fold", 3, false);
            repository.save(paperAirplane);
            paperAirplane = PaperAirplane(null, "Big-ol-wad", 100, true);
            repository.save(paperAirplane);
        }
    }

}

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}
```

This file is the main entry point into the Spring Boot application, via the `main()` method at the bottom of the file, which runs the `DemoApplication` class that is annotated with `@SpringBootApplication`. Within that class is an initialization function that loads three paper airplane designs into the paper airplane repository as the application loads. This is so that the application has some data to demonstrate its function. The class `PaperAirplaneRepository` is a class you’ll define in a moment.

Next create a `SecurityConfiguration` class.

`src/main/kotlin/com/example/demo/SecurityConfiguration.kt`

```kotlin
package com.example.demo

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration : WebSecurityConfigurerAdapter() {

    // Disable CORS and enable HTTP Basic
    override fun configure(http: HttpSecurity) {
        http
            .cors().and().csrf().disable()
            .authorizeRequests().anyRequest().authenticated()
            .and()
            .httpBasic();
    }

    // Create two users: admin and user
    @Bean
    fun users(): UserDetailsService {
        val user = User.builder()
            .username("user")
            .password("{noop}user")
            .roles("USER")
            .build()
        val admin = User.builder()
            .username("admin")
            .password("{noop}admin")
            .roles("USER", "ADMIN")
            .build()
        val test = User.builder()
            .username("test")
            .password("{noop}test")
            .roles("USER", "TEST")
            .build()
        return InMemoryUserDetailsManager(user, admin, test)
    }

}
```

This class configures most of the Spring Security options for the application. In the `configure(http: HttpSecurity)` function, it disables CORS and Cross-Site Request Forgery detection while also enabling HTTP Basic authentication for all requests. You do this to simplify testing on your computer.

The `UserDetailsService` bean configures three users using an in-memory user manager: `user`, `admin`, and `test`. The `{noop}` in the password param just tells Spring Boot to save the user password in plain text. It is not part of the actual user password.

All of this is **only for testing and demonstration purposes** and is not at all ready for prime time. In a production application, you should not be disabling CORS and CSRF, you should not be using HTTP Basic, and you should not be using hard-coded, in-memory user credentials.

Two annotations are used to help enable security on the Spring Boot application.

```kotlin
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
```

The annotation `@EnableWebSecurity` tells Spring Boot to look in this class for the `configure(http: HttpSecurity)` function, which is where our basic security options are configured. The second annotation, `@EnableGlobalMethodSecurity(prePostEnabled = true)` enables method-level security via the `@PreAuthorize` annotation, which as you’ll see demonstrated in a moment, allows you to move the method-level authorization logic from this configuration function to the controller methods themselves.

Create a data model file called `PaperAirplane.kt`.

`src/main/kotlin/com/example/demo/PaperAirplane.kt`

```kotlin

package com.example.demo

import org.springframework.data.repository.CrudRepository
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

// Defines the data model
@Entity
data class PaperAirplane(
    @GeneratedValue @Id var id: Long? = null,
    var name: String,
    var folds: Int,
    var isTesting: Boolean = true
    )

// Enables persistence of PaperAirplane entity data model
interface PaperAirplaneRepository : CrudRepository<PaperAirplane, Long> {
    fun findByName(name: String): PaperAirplane?
    fun findByIsTesting(value: Boolean): List<PaperAirplane>?
}

```

This class defines both an entity and a repository. The entity, marked by the `@Entity` annotation, is what defines the data model. The data model has four properties, including one auto-generated `id` parameter that will be mapped to the `id` column in a database table. The other properties define the parameters of our paper airplane instances. The last property, `isTesting` , is the property we will use in the feature flag later. It marks the paper airplane design as being in testing.

In the application class, you will remember, three paper airplane designs are initialized: a bi-fold, a tri-fold, and a super-secret “big-ol-wad” with a thousand folds that is still in testing. In this tutorial, imagine that the first two designs are already deployed in production and the third is still in testing. First, directly below, you will see how to implement a secure resource server that controls access based on user roles. After that, you will use feature flags to control the rollout of the test design to the `test` user before deploying it to all users (simulated here by `user`).

The repository, a subclass of Spring Boot’s `CrudRepository`, is what handles mapping the data model class to the database table. It comes with some standard persistence functions for creating, deleting, retrieving, and updating persisted entities ([take a look at the API docs](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html)).

This interface can also be extended using a natural language API. This is what is used in the two methods contained within the interface: `findByName` and `findByIsTesting`, which add the ability to query by `name` and `isTesting`. Notice how no actual implementation has to be provided. Simply defining the method name according to the query language exposes the desired functionality. The [Spring docs](https://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html) do a good job of covering the query language.

Finally, add a controller class named `PaperAirplaneController`.

`src/main/kotlin/com/example/demo/PaperAirplaneController.kt`

```kotlin

package com.example.demo

import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import java.security.Principal
import java.util.*

@RestController
class PaperAirplaneController(val repository: PaperAirplaneRepository) {

    @GetMapping("principal")
    fun info(principal: Principal): String {
        return principal.toString();
    }

    @GetMapping
    @PreAuthorize("hasAuthority('ROLE_USER')")
    fun index(principal: Principal): List<PaperAirplane>? {
        return repository.findAll().toList()
    };

    @PostMapping
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    fun post(@RequestParam name: String, @RequestParam folds: Int, @RequestParam isTesting: Boolean, principal: Principal): PaperAirplane {
        val paperAirplane = PaperAirplane(null, name, folds, isTesting);
        repository.save(paperAirplane);
        return paperAirplane;
    }

    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @PutMapping
    fun post(@RequestParam name: String, @RequestParam folds: Int, @RequestParam id: Long, @RequestParam isTesting: Boolean, principal: Principal): ResponseEntity<PaperAirplane> {
        val paperAirplane: Optional<PaperAirplane> = repository.findById(id)
        return if (paperAirplane.isPresent) {
            paperAirplane.get().name = name
            paperAirplane.get().folds = folds
            paperAirplane.get().isTesting = isTesting
            repository.save(paperAirplane.get())
            ResponseEntity.ok(paperAirplane.get())
        } else {
            ResponseEntity.notFound().build()
        }
    }

    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @DeleteMapping
    fun delete(@RequestParam id: Long, principal: Principal): ResponseEntity<String> {
        return if (repository.existsById(id)) {
            val response = repository.deleteById(id);
            print(response);
            ResponseEntity.ok("Deleted");
        } else {
            ResponseEntity.notFound().build();
        }
    }
}
```

This controller class defines the HTTP endpoints for the REST interface. Notice that the `PaperAirplaneRepository` repository is included in the class using Spring’s dependency injection in the class constructor. The controller includes mappings for HTTP POST, GET, PUT, and DELETE (create, read, update, and delete).

The `@PreAuthorize` annotation is used to restrict the GET method to authorized users with the role `USER` and restrict the PUT, POST, and DELETE methods to users with the role `ADMIN`.

There is also a `/principal` endpoint that has nothing to do with the CRUD methods for paper airplanes but instead will return the text of the `Principal.toString()` method. The `Principal` object is what represents the authenticated user and it can be educational and helpful to see what this contains. Again, this is not something you would have in a production application, but very helpful for education and testing.

## Test the Secure App

Run the application by opening a Bash shell at the project root and running the following command.

```bash

./gradlew bootRun

```

You should see output that ends in something like the following.

```bash

2021-10-20 17:39:45.057 INFO 160959 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-10-20 17:39:45.064 INFO 160959 --- [main] com.example.demo.DemoApplicationKt : Started DemoApplicationKt in 2.335 seconds (JVM running for 2.581)
2021-10-20 17:39:48.050 INFO 160959 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-10-20 17:39:48.050 INFO 160959 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-10-20 17:39:48.051 INFO 160959 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
```

Open a second Bash shell and use HTTPie to run a GET on the home endpoint.

```bash

http :8080
```

You’ll see `401 Unathorized` error. This is expected because you didn’t provide any user credentials.

Try it again using the first user (`user`).

```bash

http -a user:user :8080
```

This time you’ll get a response that contains the three paper airplanes defined in the initialization function.

```bash

HTTP/1.1 200 
...

[
    {
        "folds": 2,
        "id": 1,
        "isTesting": false,
        "name": "Bi-Fold"
    },
    {
        "folds": 3,
        "id": 2,
        "isTesting": false,
        "name": "Tri-Fold"
    },
    {
        "folds": 100,
        "id": 3,
        "isTesting": true,
        "name": "Big-ol-wad"
    }
]

```

Take a look at the `/principal` endpoint.

```bash

http -a user:user:8080/principal

```

You will see output like the following. This is the text from the `Principal.toString()` method. The `Principal` object in Spring represents the authenticated user, and it can be helpful to see what kind of properties it contains. The last entry, `Granted Authorities`, is particularly helpful as this is what the `@PreAuthorize` annotations will look for when the SpEL (Spring Expression Language) snippet `hasAuthority` is used. You can see that `user` has the granted authority `ROLE_USER`. This authority is automatically generated because when the user was created it was assigned to the role `user`.

```bash

HTTP/1.1 200 
...
UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_USER]]

```

If you use the same endpoint to look at the `admin` user you’ll see that `admin` has both `ROLE_USER` and `ROLE_ADMIN` authorities, which is why you can use the `admin` user on the POST, PUT, and DELETE methods protected by `@PreAuthorize("hasAuthority('ROLE_USER')")`.

```bash

http -a admin:admin POST :8080/info
```

```bash
HTTP/1.1 200 
...
UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=admin, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN, ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ADMIN, ROLE_USER]]

```

Use the `admin` user to add a fourth paper airplane via the POST method.

```bash

http -a admin:admin POST :8080 name==Testing folds==4 isTesting==true
```

```bash
HTTP/1.1 200 
...

{
    "folds": 4,
    "id": 4,
    "isTesting": true,
    "name": "Testing"
}

```

**Notice the double equals signs in the parameters in the command above**. This is not a mistake but is how HTTPie marks parameters to be included as query string parameters, which is what our Spring Boot methods are expecting because they use the `@RequestParam` annotations.

You can get the home endpoint again, using either user, and verify that the new paper airplane has been added.

```bash

http -a user:user :8080`
```

```bash
HTTP/1.1 200 
...

[
    {
        "folds": 2,
        "id": 1,
        "isTesting": false,
        "name": "Bi-Fold"
    },
    {
        "folds": 3,
        "id": 2,
        "isTesting": false,
        "name": "Tri-Fold"
    },
    {
        "folds": 100,
        "id": 3,
        "isTesting": true,
        "name": "Big-ol-wad"
    },
    {
        "folds": 4,
        "id": 4,
        "isTesting": true,
        "name": "Testing"
    }
]
```

You can also delete paper airplanes.

```bash

http -a admin:admin DELETE :8080 id==1
```

```bash
HTTP/1.1 200 
...

Deleted

```

And update them (using PUT). The command below changes the `isTesting` value on the `Tri-Fold` paper airplane from `false` to `true`.

```bash

http -a admin:admin -f PUT :8080 name=Tri-Fold folds=3 isTesting=true id=2`
```

```bash
HTTP/1.1 200 
...

{
    "folds": 3,
    "id": 2,
    "isTesting": true,
    "name": "Tri-Fold"
}
```

Notice that PUT **requires that you send values for all of the data model properties** and uses the `id` value as the key. You cannot just send values for the property you want to update.

Finally, you can verify that `user` cannot access the `admin` methods.

```bash
http -a user:user DELETE :8080 id==1`
```

```bash
HTTP/1.1 403 
...

{
    "error": "Forbidden",
    "path": "/",
    "status": 403,
    "timestamp": "2021-10-21T01:04:16.232+00:00"
}
```

At this point, you have a secure (for our demonstration purposes) application with full CRUD functionality. The data model objects, the paper airplanes, are being automatically mapped from the Kotlin entity class to the H2 in-memory database. Because the database is an in-memory database, it’s getting erased every time you stop and restart the application. This is actually handy for testing and demonstration purposes but in a production scenario, you’d want to connect this database to something that persists between sessions, such as an SQL or MySQL database.

## Create the Treatment on Split

Now you’re going to take a break from coding for a moment and create your treatment on Split. You can think of a **treatment** as a decision point in the code. It’s also known as a **split**. It’s a point of logic in the code that can be used to determine application behavior. A **treatment** is a specific value that the **split** can take.

For example, you’re going to create a simple boolean split that can have two treatments: `on` and `off`. However, as you’ll see later, splits do not need to be boolean and can have any number of values encoded by strings, so really, it’s up to you as to how you use them.

Splits are a little like the very old-school way of using boolean flags to control hard-coded options in code. However, they are far more powerful. Because the feature flags are hosted on Split’s servers, they can be updated in real-time. Thus feature flags allow you to modify application behavior in real-time via Split’s control panel.

Further, you can create very advanced **segments** , or specific populations of users, that have specific treatment states. Split gives you great flexibility in how you define treatment states and segment populations.

Using this technology, it is possible, for example, to roll out a set of test features to a small subset of users. If the test goes well, you can then roll the features out to more users. If the test does not go well, you can roll back the new features. The best part is: you can do all of this without compiling or deploying any code.

In this application, you’re going to pretend that one of the paper airplane designs is still in testing, the `big-ol-wad` with a 1000 folds (this is just a big wadded up ball, but don’t tell anyone, it’s a trade secret). You will use the split to initially roll out this test feature only to the `test` user. Once this test is successful, you will update the split and see how you can dynamically roll the new design out to all the users.

Practically speaking, in the code, you’ll use the Split Client to send the user information to the Split servers and retrieve the treatment (`on` or `off` state of the split), which you’ll use to determine application behavior.

To create the split, log into your [Split developer dashboard](https://app.split.io).

Click the **Create Split** button.

![2](https://www.split.io/wp-content/uploads/2021/10/2-3-948x1024.png?width=948)

Make sure the **Environment** at the top says `Staging-Default`. This must match the API keys you use in the Split Client later.

Give the split a **name** : `isTesting` Select the **Traffic Type** `user`. Click **Create**.

![3](https://www.split.io/wp-content/uploads/2021/10/3-1-1024x972.png?width=1024)

The `isTesting` split page should already be shown. If not, go to **Splits** and select the `isTesting` split in the list of splits.

Click **Add Rules** to add targeting rules for the split. Targeting rules are how the split determines what state it returns for a given query by the Split Client.

Under the **Set targeting rules** heading, click **Add rule**. Click on the drop-down that says `is in segment` and select `string` and `is in list`. Type `test` in the adjacent text field. Change the **serve** value to `on`.

![4](https://www.split.io/wp-content/uploads/2021/10/4-2-1024x376.png?width=1024)

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

That’s it. You created the split. Now you need to add the Split Client to the application.

## Integrate the Split Client in the App

The first thing is to add the Split dependency to the `build.gradle` file.

```bash

dependencies {
    ...
    implementation("io.split.client:java-client:4.2.1")
}
```

Next, create a `SplitConfiguration` Kotlin class that will handle configuring the Split Client and creating the Spring Bean so that it will be available for dependency injection. This represents current best practices on how to configure and use the Split Client in Spring Boot for most applications.

`src/main/kotlin/com/example/demo/SplitConfiguration.kt`

```kotlin

package com.example.demo

import io.split.client.SplitClient
import io.split.client.SplitClientConfig
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
class SplitConfig {

    @Value("\${split.io.api.key}")
    private val splitApiKey: String? = null

    @Bean
    @Throws(Exception::class)
    fun splitClient(): SplitClient {
        val config = SplitClientConfig.builder()
            .setBlockUntilReadyTimeout(10000)
            .enableDebug()
            .build()
        val splitFactory = SplitFactoryBuilder.build(splitApiKey, config)
        val client = splitFactory.client()
        client.blockUntilReady()
        return client
    }

}

```

You need to add your Split API key to your `application.properties` file. The API key you add here must be from the same environment in which you created the split above (`Staging-Default`, if you followed the instructions).

To find your API key, open your [Split developer dashboard](https://app.split.io).

Click on the icon in the upper left that says `DE` (for “Default” workspace).

Click on **Admin settings** and then **API keys**.

Keys are divided by both **type** and **environment**. There are `client-side` keys and `server-side` keys. There may also be keys for each environment, `Staging-Default` and `Production-Default`, if you are using the default environments created for you.

You want to copy the `server-side` and `Staging-Default` key. Click the **Copy** link. You are using the `server-side` key because you are using a server-side technology (Java and Kotlin). If you were creating a Javascript-based client application with a Vue or React front end, you would need to use the client-side keys for the client application.

Open `src/main/resources/application.properties` and add the following line, replacing `{yourApiKey}` with your actual API key.

```properties

split.io.api.key={yourApiKey}
```

To use the Split Client you need to use Spring’s dependency injection to make the bean available. Just like was done with the `PaperAirplaneRepository`, you can add it to the class constructor. Modify the class constructor by adding the Split Client, as shown below. Also add the `import` statement.

```kotlin

import io.split.client.SplitClient
...
class PaperAirplaneController(val repository: PaperAirplaneRepository, val splitClient: SplitClient) {
...
}
```

Next, update the GET method to use the Split Client.

```kotlin

@GetMapping
@PreAuthorize("hasAuthority('ROLE_USER')")
fun index(principal: Principal): List<PaperAirplane>? {
    val treatment: String = splitClient.getTreatment(principal.name, "isTesting");
    println("username=${principal.name}, treatment=$treatment")
    return if (treatment == "on") {
        repository.findAll().toList()
    } else {
        repository.findByIsTesting(false)
    }
}
```

In the method above, the treatment state is being used to determine if all of the paper airplanes are going to be returned (treatment state equals `on`), including the “test” paper airplanes; or, alternatively, if the treatment state is `off`, only the non-test paper airplanes will be returned.

The `getTreatment()` method is what returns the treatment state that determines the application behavior. This is the feature flag or split in the code. The method takes two values: the username and the name of the split. This method can also take an array of user attributes, so any arbitrary user data can be used to generate treatments, not just the username.

Because the `test` user was added to the targeting rule list, the `test` user should return the `on` treatment while the `user` user should return the `off` treatment.

The `getTreatment()` method is very fast, requiring only milliseconds. This is because treatment values are cached and updated asynchronously behind the scenes. Therefore when the `getTreatment()` method is called, it’s generally not actually making a slow network request but just returning the appropriate cached value. However, these values are still updated in near real-time.

## Test the Split

Now you can test the split. You can make the GET request with the `admin` and `user` users and see how the application will return different values based on the returned treatments.

Stop your app if it is still running using `control-c` and start it again.

```bash

./.gradlew bootRun

```

First, (in a different Bash shell) try `user`.

```bash

http -a user:user :8080
```

You’ll only get the non-test paper airplanes.

```bash

HTTP/1.1 200 
...
[
    {
        "folds": 2,
        "id": 1,
        "isTesting": false,
        "name": "Bi-Fold"
    },
    {
        "folds": 3,
        "id": 2,
        "isTesting": false,
        "name": "Tri-Fold"
    }
]

```

Now try `test`.

```bash

http -a test:test :8080
```

All of the paper airplanes are returned.

```bash

HTTP/1.1 200 
...
[
    {
        "folds": 2,
        "id": 1,
        "isTesting": false,
        "name": "Bi-Fold"
    },
    {
        "folds": 3,
        "id": 2,
        "isTesting": false,
        "name": "Tri-Fold"
    },
    {
        "folds": 100,
        "id": 3,
        "isTesting": true,
        "name": "Big-ol-wad"
    }
]
```

This is a very simple example of how application behavior can be determined using feature flags and split. The first user, `user`, is not in the targeting rules for the split and so gets served the default treatment, which is `off`. This value is used in the Kotlin controller class to filter out the test paper airplanes. The `test` user, however, receives the `on` treatment, and this is the signal for the controller to return all of the paper airplanes, including the test ones.

## Update the Split in Real-Time

Imagine you were testing the test paper airplanes with the `test` user and they were successful. Now you want to deploy them to all of the users. Using feature flags, you’ll be able to do this dynamically without having to rebuild or redeploy any code.

Go to your Split dashboard. Click **Splits** and select the `isTesting` split.

Scroll down to the section **Set the default rule**. Change this to `on`. You can also change the section called **Set the default treatment** so that the default treatment is `on` as well.

![5](https://www.split.io/wp-content/uploads/2021/10/5-2-1024x379.png?width=1024)

It’s helpful to understand what these two settings configure. The default rule specifies which treatment will be applied if none of the targeting rules specify a treatment. This assumes a successful call to `getTreatment()` from the Split Client. The default treatment is a fallback treatment that will be assigned in case anything else goes wrong, such as if there’s a network failure or if the split is killed.

The changes to the treatment rules are not live until you have saved and confirmed the changes. Click **Save changes** at the top of the panel. Click **Confirm**.

Once you’ve made this update, you can try `user` again and you will see that all of the paper airplanes will be returned.

```bash
http -a user:user :8080
```

```bash
HTTP/1.1 200 
...
[
    {
        "folds": 2,
        "id": 1,
        "isTesting": false,
        "name": "Bi-Fold"
    },
    {
        "folds": 3,
        "id": 2,
        "isTesting": false,
        "name": "Tri-Fold"
    },
    {
        "folds": 100,
        "id": 3,
        "isTesting": true,
        "name": "Big-ol-wad"
    }
]
```

## Update Split to Use User Attributes

As I mentioned, splits don’t have to simply use the username to determine the treatment state. You can send a map of user attributes in the `getTreatment()` function and these can be used to determine the returned value.

Next, you’ll create a new split and modify the GET function to demonstrate this ability by creating a split with four possible treatment values: `every`, `none`, `test`, and `non-test`. For simplicity’s sake, the attribute will simply be a value passed in through a query parameter to the GET method.

Change the GET method in the `PaperAirplaneController` class to match the following.

```kotlin

@GetMapping
@PreAuthorize("hasAuthority('ROLE_USER')")
fun index(principal: Principal, @RequestParam testAttribute: String): List<PaperAirplane>? {
    // Create the attribute map
    val attributes: Map<String, String> = mapOf("testAttribute" to testAttribute)
    // Pass the attribute to the getTreament() method
    val treatment: String = splitClient.getTreatment(principal.name, "moreTesting", attributes)
    println("username=${principal.name}, testAttribute=${testAttribute}, treatment=$treatment")
    return if (treatment == "every") {
        repository.findAll().toList()
    }
    else if (treatment == "non-test") {
        repository.findByIsTesting(false)
    }
    else if (treatment == "test-only") {
        repository.findByIsTesting(true)
    }
    else { // none
        return emptyList();
    }
}
```

Open your Split dashboard.

From the left menu, select **Splits** and click the **Create split** button.

For the split **name** , call it `moreTesting`.

Set the **Traffic Type** to `user`.

Click **Create** to create the new split.

Click **Add rules** in the new split panel.

Under **Define treatments** , you want to change the treatments so that there are four treatments: `every`, `none`, `non-test`, and `test-only`. For two of the values you can change the two existing values, but for the other two, add two new treatments using the **Add treatment** button.

![6](https://www.split.io/wp-content/uploads/2021/10/6-2-1024x549.png?width=1024)

Remember that these treatment values are just string identifiers. What they mean is arbitrary and up to you and how you use them in your application.

Scroll down to **Set targeting rules**.

Click **Add rule** four times to add four new rules.

Configure the four rules to read as follows:

1. If user attribute `testAttribute` (string) matches `AAA` serve `every`.
2. If user attribute `testAttribute` (string) matches `BBB` serve `none`
3. If user attribute `testAttribute` (string) matches `CCC` serve `non-test`
4. If user attribute `testAttribute` (string) matches `DDD` serve `test-only`

![7](https://www.split.io/wp-content/uploads/2021/10/7-2-1024x847.png?width=1024)

In the section **Set the default rule** , change the default treatment from `none` to `non-test`. Also under **Set the default treatment** , change the value to `non-test`.

![8](https://www.split.io/wp-content/uploads/2021/10/8-2-1024x378.png?width=1024)

Save the treatment by clicking **Save changes** at the top of the panel. Click **Confirm**.

Go back to your Kotlin Spring Boot application. If it’s running, stop it using `control-c` and start it again.

```bash

./gradlew bootRun

```

Once that has finished loading, in a separate Bash shell, you can make some requests to test the attribute-based splits.

For example, the following will authenticate as `user` with the `testAttribute` attribute equal to `AAA`. Because of the targeting rules you just defined, this will serve the `every` treatment, which will cause the app to return all of the paper airplanes from the database without any filtering.

```bash

http -a user:user :8080 testAttribute==AAA
```


```bash
HTTP/1.1 200 
...
[
    {
        "folds": 2,
        "id": 1,
        "isTesting": false,
        "name": "Bi-Fold"
    },
    {
        "folds": 3,
        "id": 2,
        "isTesting": false,
        "name": "Tri-Fold"
    },
    {
        "folds": 100,
        "id": 3,
        "isTesting": true,
        "name": "Big-ol-wad"
    }
]
```

If you pass in the `BBB` for the treatment value, you’ll get the `none` treatment and no paper airplanes.

```bash

http -a user:user :8080 testAttribute==BBB
```


```bash
HTTP/1.1 200 
...
[]
```

Similarly, you can perform requests for the other attribute values. I won’t reproduce the output here but it should be pretty clear that `CCC` will return only the non-test paper airplanes and `DDD` will return only the test ones.

```bash

http -a user:user :8080 testAttribute==CCC
```


```bash
http -a user:user :8080 testAttribute==DDD

```

This example is, of course, a little contrived in the sense that typically you wouldn’t be passing in the attributes via query parameters. This example is designed for coding clarity and simplicity in demonstration. Instead, in a real scenario, these attributes would be attributes of your user. They might represent membership in a testing group, geographical location, time as a customer, subscriber level, etc. — basically any attribute on which you want to segment your user population so that you can modify application behavior in real-time.

## Wrapping Up

Next steps for this tutorial might be to implement metrics tracking and event monitoring in Split. Arbitrary events can be sent to Split servers via the `SplitClient.track()` method. This method takes three params: a String key, a traffic type value, and an event type value. Here are a couple of examples from [the Split docs](https://help.split.io/hc/en-us/articles/360020585772-Events).

```kotlin

// If you would like to send an event without a value 
val trackEvent: boolean = client.track("key", "TRAFFIC_TYPE", "EVENT_TYPE")
// Example
val trackEvent: boolean = client.track("john@doe.com", "user", "page_load_time")
```

We don’t have time to go into much more detail about events here. Split has the ability to calculate metrics based on event data and even trigger alarms or change treatment states based on the calculated metrics.

In this tutorial, you created a sample resource server with full CRUD capabilities. The server was protected with Spring Security and utilized Spring Data to map a Kotlin class to an in-memory relational database. You used Split’s implementation of feature flags to see how features can be controlled at runtime dynamically without having to redeploy code. You also saw how arbitrary user attributes can be used with the Split API to control the treatment a feature flag returns.

## Learn More About Spring Boot

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/)  
[Build a CRUD App in Spring Boot with MongoDB](https://www.split.io/blog/crud-spring-boot-mongodb/)  
[Reduce Cycle Time with Feature Flags](https://www.split.io/blog/reduce-code-cycle-time-feature-flags/)  
[Feature Monitoring in 15 Minutes](https://www.split.io/blog/feature-monitoring-tutorial/)

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