Building Spring apps with Kotlin
Updated: 20 October 2023
This post outlines the general methodology and operation of Spring applications with Kotlin. For the sake of example we will be building a project management application backend
References
About Spring
The Spring framework comes with lots of infrastructure for working with some common application needs like dependency injection, transaction management, web apps, data access, and messaging
Spring boot provides an opinionated setup for building Spring applications using the framework
Initializing an Application
To initialize an application we can use the Spring initializr and select the project configuration that we would like. For this case adding selecting Maven, Kotlin, and Java 17 along with the Spring Web Dependency
Running the Application
The initialized application will have the Spring dev tools preinstalled. You can run the application in hot reloading mode using them as follows:
While you can probably make all this stuff work using the command line but since the Kotlin experience seems kinda wack in VSCode I’ll be using IntelliJ for this
So, with that noted - just hit the play button
Defining a Controller
To define a controller we can simply create a class with the @RequestController
to register the controller
and @RequestMapping
to register the endpoint within this controller
Then, we can define a method in that controller, the name of the function is not super important but the annotation at the top tells how it will be routed
The code for the controller can be seen below
HelloController.kt
We can make the endpoint return data instead of just a string by defining a data class to return:
Project Structure Overview
Some of the important parts of the generated project are the following:
mvnw
orgradlew
files which are wrappers for executing commands for the respective build toolpom.xml
orbuild.gradle
files for configuring application dependencies- The
main
function ni themain/kotlin
directory, this will have the same name as your project, and is the application entrypoint - The files in the
test
directory are where we will include our unit tests
For reference, the content of my main file are as follows:
Models
The data layer for our application will consist of different models. We can define these inside of our application
code, for example we can create a class called Project
which we can define as a data class as such:
models/Project.kt
Data Sources
Data sources are retrieval and storage (aka database). This will allow us to provide us some method of getting data and exchanging implementations of our storage layer
To create a data source we can create a class for a specific data source. When doing this, we usually want to create a
base interface that different data sources should implement. Spring calls these Repositories
, so we’ll name them using
that convention as well
The interface can be defined as so:
data/IProjectRepository.kt
Thereafter, we can create a simple in-memory implementation of the above interface using the following:
It is also important to note that the repository implementation has the @Repository
attribute
Testing
Testing will be done using JUnit
. Creating test classes can be done by adding the relevant file into the test/kotlin
directory
We can add a test for the repository we created as follows:
test/.../data/InMemoryProjectRepositoryTests.kt
We can run the above test using IntelliJ, the result will be that the test is failing since we have not added any items into our repository, we can add som sample items and run the test again, it should now be passing
The changes to the repository data
definition can be seen below:
Services
Services are defined using a class with the @Service
annotation and generally imply that we will be using some kind of
data service or other data.
@Service
and @Repository
are extensions on the @Component
implementation which is some kind of injectable class
that can be managed by Spring. These are all instances of an @Bean
We can define a class for the service which uses a project repository as follows:
services/ProjectService.kt
Controllers
Controllers are used for mapping services to HTTP endpoints. We can define a controller like so:
Get All
In the above we are using @RestController
and @RequestMapping
to define the controller and @GetMapping
to annotate
the method for the defined handler
We can further defined tests for this controller using @SpringBootTest
which will initialize the entire application
context for the purpose of a test. We can also restrict this a bit using Test Slices, but the example below just uses
the entire Spring application context
In the below test
The test file can be seen below in which we verify that the HTTP response matches what we would like as well as ensuring that the data we received is aligned to the data we expect by way of converting the expected data to the JSON response
Additionally, we will use MockMvc
and @AutoConfigureMockMvc
to mock HTTP requests without all the HTTP request
overhead. Using @Autowired constructor
with this uses the Spring boot constructor dependency injection for the
relevant members in the constructor
Get One
Using the methodology above we can implement controllers for getting one project by doing the following:
- Update the project entity to have an
id
field:
- Update the repository to contain a method for getting a project by ID:
- Update the repository implementation to use th
id
field and to get one byid
:
In the above snippet, it is important to note that we are returning a
Nullable
project, this is because we may not find a value with the givenid
- Update the service to allow getting a value by
id
:
- Add a new endpoint on the controller that uses the
id
as aPathParam
which also contains the ID in the@GetMapping
definition. In the controller, we will return aResponseEntity
with aNotFound
status for a result that is not found, or anOK
status for a result that is found
Query Params
We can handle query params in our application by using the RequestParam
annotation:
Note that in the above example
name
is of typeString?
, if we were to make this a required type ofString
then the application would return a bad request if the consumer does not provide a value for the query parameter
Furthermore we can update our other members of our implementation to use this search parameter as follows:
In the service:
In the repository interface:
And in the reposotry implemetation:
Note that the
searchProjects
method here requires an optional string which we default in thecontains
function, the reason we do not default the value in the function parameter list is that it is not allowed when overriding a method to define a default in the method param. However under other circumstances we can default a parameter by defining it like so:fun doStuff(text: String? = "")
Exceptions
If for some reason we are unable to use pattern matching and more functional methods for handling excpetions, Spring
Boot allows us to define an ExceptionHandler
for our controller, for example, if we were to have some codepath throw
an error like NoSuchElement
Or if we were to handle a more general exception being thrown:
POST and PUT endpoints
We can define endpoints that use POST or PUT using the relevant mapping annotation. In order to receive data in the body
we would also use the @RequestBody
annotation. Furthermore we can use the @ResponseStatus
to annotate that status
that will be returned to the user when the response is sent:
Note that you would still need to implement any service or repository level tasks as needed
Other HTTP Methods
As needed, we can also implement other HTTP methods like PATCH
and DELETE
similar to the POST
and GET
methods
above respectively