Spring boot loves Kotlin

After creating a lot of Kotlin Android applications in these last two years, we want to continue coding using Kotlin in the server side. We started a small project to test Spring boot running an application using Spring and third-party libraries. It was delightful, and we want to share with you our impressions.

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can 'just run'.

That means you can create Spring applications with an embedded Tomcat, with no XML. Spring automatically configures itself with 3rd party libraries. It stills use annotations, but in an Android way, in the end, we are used to them for libraries such as Dagger and Retrofit.

We decide to give Spring a chance because they offer full support for Kotlin, keep in mind it’s a huge 16-years-old enterprise framework which supports lots of Java specifications and, on top of that, we can use lots of mature tools in Kotlin.

With every release we have better Kotlin support, coding Spring applications without boilerplate. You can follow the complete Spring tutorial here.

We created an example project, based on the Kotlin adventure in Play Framework blog post, to show you all the features we considered, we use Arrow (a functional programming library for Kotlin) lightly, only using Try datatype. Don’t fear algebraic datatypes!.

To better present you our solution, we are going to show you some code snippets extracted from the project you can find in this Github repository: SpringBootKotlin

Controllers

Controllers are really simple, by just adding @RestController to our class, Spring knows which ones are controllers and what dependencies they need.

@RestController
class DeveloperController(private val getKarumiDeveloper: GetDeveloper) {  

    @GetMapping("/developer/{developerId}")  
	fun getDeveloper(@PathVariable developerId: UUID): Developer =      
		getKarumiDeveloper(developerId).orThrow()

}

@GetMapping and @PathVariable are used to define the REST paths. Controllers call to the use cases which handles all the application business rules following Clean Architecture.

Storage

We use the H2 in-memory database; now it’s time to use Spring Data JPA to handle the database easily with zero boilerplate using repositories. The repository is an abstraction which gives us a sophisticated CRUD functionality for the entity class where you can extend it with more query methods adding keywords in the method names for the JPA to understand our queries.

@Component
interface DeveloperRepository : CrudRepository<DeveloperEntity, UUID> { 
    fun findByUsernameIgnoreCase(username: String): DeveloperEntity?
}

Using CrudRepository<T, UUID> we get all CRUD methods such as findAll, save, delete… We use one query method to get a developer by their username: using the keyword findBy followed by a field of our DeveloperEntity, their username. Besides, adding IgnoreCase keyword will make matches for usernames ignoring casing. JPA understands that modifier and implements it for us. There are more repositories you can take a look if you are interested, like PagningAndSortingRepository and JpaRepository.

Domain

Our domain implementation is similar to any other example using Clean Architecture. There is one significant change, though, we realized that Spring boot uses exceptions to handle application errors and it catches all exceptions and parses them to HTTP errors. For that reason, we decided to use the Try datatype and define errors as exceptions with the respective HTTP status code, associated with an annotation.

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
object StorageError : RuntimeException("Internal storage error")

@ResponseStatus(HttpStatus.NOT_FOUND)
object NotFound : RuntimeException("Developer not found")

@ResponseStatus(HttpStatus.BAD_REQUEST)
object NotKarumier : RuntimeException("Developer isn't karumier")

@ResponseStatus(HttpStatus.CONFLICT)
object AlreadyRegistered : RuntimeException("Developer already registered")

With this solution, we map common exceptions to our domain exceptions using the Try datatype and throw it at the end of the execution. Spring boot automatically transforms our domain errors to HTTP error codes.

Now, if we revisit the function controller, we use the method orThrow() that throws an exception if there is any Failure(Throwable) defined before, or return the value we expect.

@GetMapping("/developer/{developerId}")  
fun getDeveloper(@PathVariable developerId: UUID): Developer =      			getKarumiDeveloper(developerId).orThrow()

Try doesn’t have this method because, in general, FP doesn’t promote the idea of throwing exceptions. In our case though, it’s useful to unsafely unbox the data type at the end of the execution so that Spring can take care of it. Here is the orThrow method implementation:

fun <A> Try<A>.orThrow(): A = fold({ throw it }, ::identity)

Dependency injection

The reason Spring has lots of annotations is that it does many things for you like inversion of control, one of the ways it does is Dependency Injection.

Spring has a crucial concept called Bean. These are all the objects Spring initiates and manages for you. Initially, a bean was an XML element, that's because Spring wants to be totally decoupled from the format in which the configuration metadata is actually written. However, from Spring 2.5 you can use Annotation-based configuration and Java-based configuration.

It supports the standard JSR-330 so if you are familiar with Dagger 2 or Guice then you already know how it works: using the @Inject annotation. Spring uses @Autowired instead, but you can still use @Inject if you want.

There are some annotations like @Service , @Repository, and @Controller that overrides @Component and enable different scopes by annotation. That means that if you want only to instantiate the controllers to do API integration tests, you don’t need to instantiate the whole dependency graph. Same for @Services which corresponds to your domain model and @Repository for your data access layer.

Tests

You can test your application as you are used to, using JUnit and running unit tests that test your small pieces of code. However, we are here to talk about Spring and its integration with Kotlin, that's why we want to show the parts of testing that need the Spring Boot test context: integration tests.

First of all, we use JUnit5 -that let us use parameter constructor injection- Spring test dependency and KotlinTest. We have to say that we had a bad experience with Play Framework that doesn’t let us use KotlinTest but we are going to talk about it and its integration later.

This time we want to give Mockk a try to create our test doubles, it has been written in Kotlin. The only problem with Mockk in Spring is that the @MockBean annotation we used for mocking automatically a bean using dependency injection is Mockito specific. For that reason we needed to use Springmockk that provides the same annotations for this new library.

testImplementation('org.springframework.boot:spring-boot-starter-test') {        exclude module: 'junit'    
}    

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.2.0'    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.2.0'
testImplementation 'com.ninja-squad:springmockk:1.1.0'

The @SpringBootTest annotation provides us lots of features on top of the Spring TestContext like:

  • Getting environment properties
  • Run the spring configuration we defined
  • Support for different web environments including the ability to start a fully running web server listening on a random port and registering a TestRestTemplate bean to use it in web tests.

When you want to make a request to your API, you have two options: use TestRestTemplate or create a MockMvc.

TestRestTemplate is an alternative to RestTemplate which is a synchronous client to perform HTTP requests easily, then you have TestRestTemplate which is suitable for integration tests.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DeveloperControllerTest @Autowired constructor(  
	private val restTemplate: TestRestTemplate,  
	private val repository: DeveloperRepository
) : StringSpec() {

	init {
 
		"developer GET should retrieve by id" {      
			val developer = create(KARUMI_DEVELOPER)      
			val result = restTemplate.getForEntity<Developer>(“$URL/developer/${developer.id}”)      
            result.statusCode shouldBe OK      
            result.body?.username shouldBe developer.username 
            result.body?.email shouldBe developer.email    
        } 
    }
 }

In this case, we get a developer by id, and we match the expected output, you can see the controller tests using TestRestTemplate using KotlinTest for an end-to-end integration tests in the DeveloperControllerTest.kt file. We use many extension functions to keep our tests clean and move the boilerplate and ceremony away.

MockMvc is a “powerful entry point for the server-side Spring MVC test support”. What does this mean? What is the difference to TestRestTemplate? It means MockMvc can make “requests” to our controllers without running a full web server. On the other hand, TestRestController needs precisely that because it makes real HTTP requests to our web server. To only test the web layer in combination with MockMvc you need to use @WebMvcTest and only instantiate all your required beans with @Controller (and derived) annotations.

"developer GET should retrieve by id" { 
	every { developerDao.getById(DEVELOPER_ID) } returns KARUMI_DEVELOPER.some().success()        
	every { developerDao.getByUsername(any()) } returns SESSION_DEVELOPER.some().success()        
	mockMvc.perform(          
		get("/developer/$DEVELOPER_ID")            	
		.contentType(MediaType.APPLICATION_JSON)            
		.withAuthorization()        
	).andExpect(status().isOk)          
	.andDo(print())          
	.matchWithSnapshot(this)      
}

This is the same example we reviewed before but this time using MockMvc. You can see the controller tests using MockMvc and KotlinTest but using spec DeveloperControllerUsingMvcMock file, in this case, we don’t do end-to-end testing. We also add KotlinSnapshot library we implemented to see the benefits of this technique in this scenario.

If you want to test your repositories, then you need to add a @DataJpaTest annotation, and then you only need to instantiate the data-access-layer beans graph. We also use @Transactional annotation to isolate our tests.

@DataJpaTest
@Transactional
class DeveloperRepositoryTest(  
	val repository: DeveloperRepository
) : StringSpec(), GivenDeveloper {  

	override fun listeners() = listOf(SpringListener)  

	init {    

		"developer should be updated" {      

			val developer = save(KARUMI_DEVELOPER)      
			val developerUpdate = developer.copy(username = "Pedro")      
			val updatedDeveloper = repository.save(developerUpdate)      	

			updatedDeveloper shouldBe developerUpdate      
			find(developer.id) shouldBe developerUpdate    
	 	}  
	}
}

Finally, our conclusion about using KotlinTest in Spring boot, and after contributing ourselves to improve the integration with @MockBean, is that it doesn't make sense to use KotlinTest for integration tests where Spring is involved. That's because Spring uses annotations to help us providing some features like authentication mock using @WithSecurityContext or cleaning the database after a single test using @Transactional. KotlinTest doesn’t support this kind of annotations, and we needed to create KotlinTest Listeners to support these features.

You can see how we used a custom annotation to mock the authentication of our controllers in DeveloperControllerTestMockUser.kt  and here the custom annotation code MockCustomUser.kt. This is the main reason we can’t use it with KotlinTest.

Conclusion

If you want to create a scalable web service with a stable framework having a huge community, tons of documentation and where Kotlin is supported then use Spring.

Spring and Kotlin community are pushing to add more Kotlin features in Spring like Coroutines. Also, since version 5, Spring Framework is supporting Kotlin officially with functional bean definitions DSL, routing DSL for Spring WebFlux, null safety and much more!

Spring boot has a lot more features we didn’t mention, if you would like to know more about this topic, please add a comment or a tweet with your feedback or questions.

At Karumi, we are glad to add more tools and skills keeping our focus on building robust software in any platform or environment. See you soon!