1128 lines
42 KiB
Plaintext
1128 lines
42 KiB
Plaintext
[[kotlin]]
|
|
= Kotlin
|
|
|
|
https://kotlinlang.org[Kotlin] is a statically typed language that targets the JVM
|
|
(and other platforms) which allows writing concise and elegant code while providing
|
|
very good https://kotlinlang.org/docs/reference/java-interop.html[interoperability]
|
|
with existing libraries written in Java.
|
|
|
|
The Spring Framework provides first-class support for Kotlin and lets developers write
|
|
Kotlin applications almost as if the Spring Framework was a native Kotlin framework.
|
|
Most of the code samples of the reference documentation are
|
|
provided in Kotlin in addition to Java.
|
|
|
|
The easiest way to build a Spring application with Kotlin is to leverage Spring Boot and
|
|
its https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-kotlin.html[dedicated Kotlin support].
|
|
https://spring.io/guides/tutorials/spring-boot-kotlin/[This comprehensive tutorial]
|
|
will teach you how to build Spring Boot applications with Kotlin using https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
|
|
|
Feel free to join the #spring channel of https://slack.kotlinlang.org/[Kotlin Slack]
|
|
or ask a question with `spring` and `kotlin` as tags on
|
|
https://stackoverflow.com/questions/tagged/spring+kotlin[Stackoverflow] if you need support.
|
|
|
|
|
|
|
|
|
|
[[kotlin-requirements]]
|
|
== Requirements
|
|
|
|
Spring Framework supports Kotlin 1.3+ and requires
|
|
https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`]
|
|
(or one of its variants, such as https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8[`kotlin-stdlib-jdk8`])
|
|
and https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`]
|
|
to be present on the classpath. They are provided by default if you bootstrap a Kotlin project on
|
|
https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
|
|
|
WARNING: Kotlin https://kotlinlang.org/docs/inline-classes.html[inline classes] are not yet supported.
|
|
|
|
NOTE: The https://github.com/FasterXML/jackson-module-kotlin[Jackson Kotlin module] is required
|
|
for serializing or deserializing JSON data for Kotlin classes with Jackson, so make sure to add the
|
|
`com.fasterxml.jackson.module:jackson-module-kotlin` dependency to your project if you have such need.
|
|
It is automatically registered when found in the classpath.
|
|
|
|
|
|
|
|
|
|
[[kotlin-extensions]]
|
|
== Extensions
|
|
|
|
Kotlin https://kotlinlang.org/docs/reference/extensions.html[extensions] provide the ability
|
|
to extend existing classes with additional functionality. The Spring Framework Kotlin APIs
|
|
use these extensions to add new Kotlin-specific conveniences to existing Spring APIs.
|
|
|
|
The {docs-spring-framework}/kdoc-api/[Spring Framework KDoc API] lists
|
|
and documents all available Kotlin extensions and DSLs.
|
|
|
|
NOTE: Keep in mind that Kotlin extensions need to be imported to be used. This means,
|
|
for example, that the `GenericApplicationContext.registerBean` Kotlin extension
|
|
is available only if `org.springframework.context.support.registerBean` is imported.
|
|
That said, similar to static imports, an IDE should automatically suggest the import in most cases.
|
|
|
|
For example, https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters[Kotlin reified type parameters]
|
|
provide a workaround for JVM https://docs.oracle.com/javase/tutorial/java/generics/erasure.html[generics type erasure],
|
|
and the Spring Framework provides some extensions to take advantage of this feature.
|
|
This allows for a better Kotlin API `RestTemplate`, for the new `WebClient` from Spring
|
|
WebFlux, and for various other APIs.
|
|
|
|
NOTE: Other libraries, such as Reactor and Spring Data, also provide Kotlin extensions
|
|
for their APIs, thus giving a better Kotlin development experience overall.
|
|
|
|
To retrieve a list of `User` objects in Java, you would normally write the following:
|
|
|
|
[source,java,indent=0]
|
|
----
|
|
Flux<User> users = client.get().retrieve().bodyToFlux(User.class)
|
|
----
|
|
|
|
With Kotlin and the Spring Framework extensions, you can instead write the following:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
val users = client.get().retrieve().bodyToFlux<User>()
|
|
// or (both are equivalent)
|
|
val users : Flux<User> = client.get().retrieve().bodyToFlux()
|
|
----
|
|
|
|
As in Java, `users` in Kotlin is strongly typed, but Kotlin's clever type inference allows
|
|
for shorter syntax.
|
|
|
|
|
|
|
|
|
|
[[kotlin-null-safety]]
|
|
== Null-safety
|
|
|
|
One of Kotlin's key features is https://kotlinlang.org/docs/reference/null-safety.html[null-safety],
|
|
which cleanly deals with `null` values at compile time rather than bumping into the famous
|
|
`NullPointerException` at runtime. This makes applications safer through nullability
|
|
declarations and expressing "`value or no value`" semantics without paying the cost of wrappers, such as `Optional`.
|
|
(Kotlin allows using functional constructs with nullable values. See this
|
|
https://www.baeldung.com/kotlin-null-safety[comprehensive guide to Kotlin null-safety].)
|
|
|
|
Although Java does not let you express null-safety in its type-system, the Spring Framework
|
|
provides <<core#null-safety, null-safety of the whole Spring Framework API>>
|
|
via tooling-friendly annotations declared in the `org.springframework.lang` package.
|
|
By default, types from Java APIs used in Kotlin are recognized as
|
|
https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types[platform types],
|
|
for which null-checks are relaxed.
|
|
https://kotlinlang.org/docs/reference/java-interop.html#jsr-305-support[Kotlin support for JSR-305 annotations]
|
|
and Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers,
|
|
with the advantage of dealing with `null`-related issues at compile time.
|
|
|
|
NOTE: Libraries such as Reactor or Spring Data provide null-safe APIs to leverage this feature.
|
|
|
|
You can configure JSR-305 checks by adding the `-Xjsr305` compiler flag with the following
|
|
options: `-Xjsr305={strict|warn|ignore}`.
|
|
|
|
For kotlin versions 1.1+, the default behavior is the same as `-Xjsr305=warn`.
|
|
The `strict` value is required to have Spring Framework API null-safety taken into account
|
|
in Kotlin types inferred from Spring API but should be used with the knowledge that Spring
|
|
API nullability declaration could evolve even between minor releases and that more checks may
|
|
be added in the future.
|
|
|
|
NOTE: Generic type arguments, varargs, and array elements nullability are not supported yet,
|
|
but should be in an upcoming release. See https://github.com/Kotlin/KEEP/issues/79[this discussion]
|
|
for up-to-date information.
|
|
|
|
|
|
|
|
|
|
[[kotlin-classes-interfaces]]
|
|
== Classes and Interfaces
|
|
|
|
The Spring Framework supports various Kotlin constructs, such as instantiating Kotlin classes
|
|
through primary constructors, immutable classes data binding, and function optional parameters
|
|
with default values.
|
|
|
|
Kotlin parameter names are recognized through a dedicated `KotlinReflectionParameterNameDiscoverer`,
|
|
which allows finding interface method parameter names without requiring the Java 8 `-parameters`
|
|
compiler flag to be enabled during compilation. (For completeness, we nevertheless recommend
|
|
running the Kotlin compiler with its `-java-parameters` flag for standard Java parameter exposure.)
|
|
|
|
You can declare configuration classes as
|
|
https://kotlinlang.org/docs/reference/nested-classes.html[top level or nested but not inner],
|
|
since the later requires a reference to the outer class.
|
|
|
|
|
|
|
|
|
|
[[kotlin-annotations]]
|
|
== Annotations
|
|
|
|
The Spring Framework also takes advantage of https://kotlinlang.org/docs/reference/null-safety.html[Kotlin null-safety]
|
|
to determine if an HTTP parameter is required without having to explicitly
|
|
define the `required` attribute. That means `@RequestParam name: String?` is treated
|
|
as not required and, conversely, `@RequestParam name: String` is treated as being required.
|
|
This feature is also supported on the Spring Messaging `@Header` annotation.
|
|
|
|
In a similar fashion, Spring bean injection with `@Autowired`, `@Bean`, or `@Inject` uses
|
|
this information to determine if a bean is required or not.
|
|
|
|
For example, `@Autowired lateinit var thing: Thing` implies that a bean
|
|
of type `Thing` must be registered in the application context, while `@Autowired lateinit var thing: Thing?`
|
|
does not raise an error if such a bean does not exist.
|
|
|
|
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, Car)` implies
|
|
that a bean of type `Toy` must be registered in the application context, while a bean of
|
|
type `Car` may or may not exist. The same behavior applies to autowired constructor parameters.
|
|
|
|
NOTE: If you use bean validation on classes with properties or a primary constructor
|
|
parameters, you may need to use
|
|
https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets[annotation use-site targets],
|
|
such as `@field:NotNull` or `@get:Size(min=5, max=15)`, as described in
|
|
https://stackoverflow.com/a/35853200/1092077[this Stack Overflow response].
|
|
|
|
|
|
|
|
|
|
[[kotlin-bean-definition-dsl]]
|
|
== Bean Definition DSL
|
|
|
|
Spring Framework supports registering beans in a functional way by using lambdas
|
|
as an alternative to XML or Java configuration (`@Configuration` and `@Bean`). In a nutshell,
|
|
it lets you register beans with a lambda that acts as a `FactoryBean`.
|
|
This mechanism is very efficient, as it does not require any reflection or CGLIB proxies.
|
|
|
|
In Java, you can, for example, write the following:
|
|
|
|
[source,java,indent=0]
|
|
----
|
|
class Foo {}
|
|
|
|
class Bar {
|
|
private final Foo foo;
|
|
public Bar(Foo foo) {
|
|
this.foo = foo;
|
|
}
|
|
}
|
|
|
|
GenericApplicationContext context = new GenericApplicationContext();
|
|
context.registerBean(Foo.class);
|
|
context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class)));
|
|
----
|
|
|
|
In Kotlin, with reified type parameters and `GenericApplicationContext` Kotlin extensions,
|
|
you can instead write the following:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
class Foo
|
|
|
|
class Bar(private val foo: Foo)
|
|
|
|
val context = GenericApplicationContext().apply {
|
|
registerBean<Foo>()
|
|
registerBean { Bar(it.getBean()) }
|
|
}
|
|
----
|
|
====
|
|
|
|
When the class `Bar` has a single constructor, you can even just specify the bean class,
|
|
the constructor parameters will be autowired by type:
|
|
|
|
====
|
|
[source,kotlin,indent=0]
|
|
----
|
|
val context = GenericApplicationContext().apply {
|
|
registerBean<Foo>()
|
|
registerBean<Bar>()
|
|
}
|
|
----
|
|
|
|
In order to allow a more declarative approach and cleaner syntax, Spring Framework provides
|
|
a {docs-spring-framework}/kdoc-api/spring-context/org.springframework.context.support/-bean-definition-dsl/index.html[Kotlin bean definition DSL]
|
|
It declares an `ApplicationContextInitializer` through a clean declarative API,
|
|
which lets you deal with profiles and `Environment` for customizing
|
|
how beans are registered.
|
|
|
|
In the following example notice that:
|
|
|
|
* Type inference usually allows to avoid specifying the type for bean references like `ref("bazBean")`
|
|
* It is possible to use Kotlin top level functions to declare beans using callable references like `bean(::myRouter)` in this example
|
|
* When specifying `bean<Bar>()` or `bean(::myRouter)`, parameters are autowired by type
|
|
* The `FooBar` bean will be registered only if the `foobar` profile is active
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
class Foo
|
|
class Bar(private val foo: Foo)
|
|
class Baz(var message: String = "")
|
|
class FooBar(private val baz: Baz)
|
|
|
|
val myBeans = beans {
|
|
bean<Foo>()
|
|
bean<Bar>()
|
|
bean("bazBean") {
|
|
Baz().apply {
|
|
message = "Hello world"
|
|
}
|
|
}
|
|
profile("foobar") {
|
|
bean { FooBar(ref("bazBean")) }
|
|
}
|
|
bean(::myRouter)
|
|
}
|
|
|
|
fun myRouter(foo: Foo, bar: Bar, baz: Baz) = router {
|
|
// ...
|
|
}
|
|
----
|
|
|
|
NOTE: This DSL is programmatic, meaning it allows custom registration logic of beans
|
|
through an `if` expression, a `for` loop, or any other Kotlin constructs.
|
|
|
|
You can then use this `beans()` function to register beans on the application context,
|
|
as the following example shows:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
val context = GenericApplicationContext().apply {
|
|
myBeans.initialize(this)
|
|
refresh()
|
|
}
|
|
----
|
|
|
|
NOTE: Spring Boot is based on JavaConfig and
|
|
https://github.com/spring-projects/spring-boot/issues/8115[does not yet provide specific support for functional bean definition],
|
|
but you can experimentally use functional bean definitions through Spring Boot's `ApplicationContextInitializer` support.
|
|
See https://stackoverflow.com/questions/45935931/how-to-use-functional-bean-definition-kotlin-dsl-with-spring-boot-and-spring-w/46033685#46033685[this Stack Overflow answer]
|
|
for more details and up-to-date information. See also the experimental Kofu DSL developed in https://github.com/spring-projects/spring-fu[Spring Fu incubator].
|
|
|
|
|
|
|
|
|
|
[[kotlin-web]]
|
|
== Web
|
|
|
|
|
|
|
|
[[router-dsl]]
|
|
=== Router DSL
|
|
|
|
Spring Framework comes with a Kotlin router DSL available in 3 flavors:
|
|
|
|
* WebMvc.fn DSL with {docs-spring-framework}/kdoc-api/spring-webmvc/org.springframework.web.servlet.function/router.html[router { }]
|
|
* WebFlux.fn <<web-reactive#webflux-fn, Reactive>> DSL with {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.server/router.html[router { }]
|
|
* WebFlux.fn <<Coroutines>> DSL with {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }]
|
|
|
|
These DSL let you write clean and idiomatic Kotlin code to build a `RouterFunction` instance as the following example shows:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Configuration
|
|
class RouterRouterConfiguration {
|
|
|
|
@Bean
|
|
fun mainRouter(userHandler: UserHandler) = router {
|
|
accept(TEXT_HTML).nest {
|
|
GET("/") { ok().render("index") }
|
|
GET("/sse") { ok().render("sse") }
|
|
GET("/users", userHandler::findAllView)
|
|
}
|
|
"/api".nest {
|
|
accept(APPLICATION_JSON).nest {
|
|
GET("/users", userHandler::findAll)
|
|
}
|
|
accept(TEXT_EVENT_STREAM).nest {
|
|
GET("/users", userHandler::stream)
|
|
}
|
|
}
|
|
resources("/**", ClassPathResource("static/"))
|
|
}
|
|
}
|
|
----
|
|
|
|
NOTE: This DSL is programmatic, meaning that it allows custom registration logic of beans
|
|
through an `if` expression, a `for` loop, or any other Kotlin constructs. That can be useful
|
|
when you need to register routes depending on dynamic data (for example, from a database).
|
|
|
|
See https://github.com/mixitconf/mixit/[MiXiT project] for a concrete example.
|
|
|
|
|
|
|
|
[[mockmvc-dsl]]
|
|
=== MockMvc DSL
|
|
|
|
A Kotlin DSL is provided via `MockMvc` Kotlin extensions in order to provide a more
|
|
idiomatic Kotlin API and to allow better discoverability (no usage of static methods).
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
val mockMvc: MockMvc = ...
|
|
mockMvc.get("/person/{name}", "Lee") {
|
|
secure = true
|
|
accept = APPLICATION_JSON
|
|
headers {
|
|
contentLanguage = Locale.FRANCE
|
|
}
|
|
principal = Principal { "foo" }
|
|
}.andExpect {
|
|
status { isOk }
|
|
content { contentType(APPLICATION_JSON) }
|
|
jsonPath("$.name") { value("Lee") }
|
|
content { json("""{"someBoolean": false}""", false) }
|
|
}.andDo {
|
|
print()
|
|
}
|
|
----
|
|
|
|
|
|
|
|
[[kotlin-script-templates]]
|
|
=== Kotlin Script Templates
|
|
|
|
Spring Framework provides a
|
|
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/script/ScriptTemplateView.html[`ScriptTemplateView`]
|
|
which supports https://www.jcp.org/en/jsr/detail?id=223[JSR-223] to render templates by using script engines.
|
|
|
|
By leveraging `scripting-jsr223` dependencies, it
|
|
is possible to use such feature to render Kotlin-based templates with
|
|
https://github.com/Kotlin/kotlinx.html[kotlinx.html] DSL or Kotlin multiline interpolated `String`.
|
|
|
|
`build.gradle.kts`
|
|
[source,kotlin,indent=0]
|
|
----
|
|
dependencies {
|
|
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
|
|
}
|
|
----
|
|
|
|
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans.
|
|
|
|
`KotlinScriptConfiguration.kt`
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Configuration
|
|
class KotlinScriptConfiguration {
|
|
|
|
@Bean
|
|
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
|
|
engineName = "kotlin"
|
|
setScripts("scripts/render.kts")
|
|
renderFunction = "render"
|
|
isSharedEngine = false
|
|
}
|
|
|
|
@Bean
|
|
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
|
|
setPrefix("templates/")
|
|
setSuffix(".kts")
|
|
}
|
|
}
|
|
----
|
|
|
|
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
|
|
project for more details.
|
|
|
|
|
|
|
|
[[kotlin-multiplatform-serialization]]
|
|
=== Kotlin multiplatform serialization
|
|
|
|
As of Spring Framework 5.3, https://github.com/Kotlin/kotlinx.serialization[Kotlin multiplatform serialization] is
|
|
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The builtin support currently targets CBOR, JSON, and ProtoBuf formats.
|
|
|
|
To enable it, follow https://github.com/Kotlin/kotlinx.serialization#setup[those instructions] to add the related dependency and plugin.
|
|
With Spring MVC and WebFlux, both Kotlin serialization and Jackson will be configured by default if they are in the classpath since
|
|
Kotlin serialization is designed to serialize only Kotlin classes annotated with `@Serializable`.
|
|
With Spring Messaging (RSocket), make sure that neither Jackson, GSON or JSONB are in the classpath if you want automatic configuration,
|
|
if Jackson is needed configure `KotlinSerializationJsonMessageConverter` manually.
|
|
|
|
|
|
|
|
|
|
[[coroutines]]
|
|
== Coroutines
|
|
|
|
Kotlin https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines] are Kotlin
|
|
lightweight threads allowing to write non-blocking code in an imperative way. On language side,
|
|
suspending functions provides an abstraction for asynchronous operations while on library side
|
|
https://github.com/Kotlin/kotlinx.coroutines[kotlinx.coroutines] provides functions like
|
|
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html[`async { }`]
|
|
and types like https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[`Flow`].
|
|
|
|
Spring Framework provides support for Coroutines on the following scope:
|
|
|
|
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html[Deferred] and https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[Flow] return values support in Spring MVC and WebFlux annotated `@Controller`
|
|
* Suspending function support in Spring MVC and WebFlux annotated `@Controller`
|
|
* Extensions for WebFlux {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.client/index.html[client] and {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.server/index.html[server] functional API.
|
|
* WebFlux.fn {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL
|
|
* Suspending function and `Flow` support in RSocket `@MessageMapping` annotated methods
|
|
* Extensions for {docs-spring-framework}/kdoc-api/spring-messaging/org.springframework.messaging.rsocket/index.html[`RSocketRequester`]
|
|
|
|
|
|
|
|
[[dependencies]]
|
|
=== Dependencies
|
|
|
|
Coroutines support is enabled when `kotlinx-coroutines-core` and `kotlinx-coroutines-reactor`
|
|
dependencies are in the classpath:
|
|
|
|
`build.gradle.kts`
|
|
[source,kotlin,indent=0]
|
|
----
|
|
dependencies {
|
|
|
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}")
|
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}")
|
|
}
|
|
----
|
|
|
|
Version `1.4.0` and above are supported.
|
|
|
|
|
|
|
|
[[how-reactive-translates-to-coroutines?]]
|
|
=== How Reactive translates to Coroutines?
|
|
|
|
For return values, the translation from Reactive to Coroutines APIs is the following:
|
|
|
|
* `fun handler(): Mono<Void>` becomes `suspend fun handler()`
|
|
* `fun handler(): Mono<T>` becomes `suspend fun handler(): T` or `suspend fun handler(): T?` depending on if the `Mono` can be empty or not (with the advantage of being more statically typed)
|
|
* `fun handler(): Flux<T>` becomes `fun handler(): Flow<T>`
|
|
|
|
For input parameters:
|
|
|
|
* If laziness is not needed, `fun handler(mono: Mono<T>)` becomes `fun handler(value: T)` since a suspending functions can be invoked to get the value parameter.
|
|
* If laziness is needed, `fun handler(mono: Mono<T>)` becomes `fun handler(supplier: suspend () -> T)` or `fun handler(supplier: suspend () -> T?)`
|
|
|
|
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[`Flow`] is `Flux` equivalent in Coroutines world, suitable for hot or cold stream, finite or infinite streams, with the following main differences:
|
|
|
|
* `Flow` is push-based while `Flux` is push-pull hybrid
|
|
* Backpressure is implemented via suspending functions
|
|
* `Flow` has only a https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/collect.html[single suspending `collect` method] and operators are implemented as https://kotlinlang.org/docs/reference/extensions.html[extensions]
|
|
* https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core/common/src/flow/operators[Operators are easy to implement] thanks to Coroutines
|
|
* Extensions allow to add custom operators to `Flow`
|
|
* Collect operations are suspending functions
|
|
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html[`map` operator] supports asynchronous operation (no need for `flatMap`) since it takes a suspending function parameter
|
|
|
|
Read this blog post about https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow[Going Reactive with Spring, Coroutines and Kotlin Flow]
|
|
for more details, including how to run code concurrently with Coroutines.
|
|
|
|
|
|
|
|
[[controllers]]
|
|
=== Controllers
|
|
|
|
Here is an example of a Coroutines `@RestController`.
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@RestController
|
|
class CoroutinesRestController(client: WebClient, banner: Banner) {
|
|
|
|
@GetMapping("/suspend")
|
|
suspend fun suspendingEndpoint(): Banner {
|
|
delay(10)
|
|
return banner
|
|
}
|
|
|
|
@GetMapping("/flow")
|
|
fun flowEndpoint() = flow {
|
|
delay(10)
|
|
emit(banner)
|
|
delay(10)
|
|
emit(banner)
|
|
}
|
|
|
|
@GetMapping("/deferred")
|
|
fun deferredEndpoint() = GlobalScope.async {
|
|
delay(10)
|
|
banner
|
|
}
|
|
|
|
@GetMapping("/sequential")
|
|
suspend fun sequential(): List<Banner> {
|
|
val banner1 = client
|
|
.get()
|
|
.uri("/suspend")
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
.awaitExchange()
|
|
.awaitBody<Banner>()
|
|
val banner2 = client
|
|
.get()
|
|
.uri("/suspend")
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
.awaitExchange()
|
|
.awaitBody<Banner>()
|
|
return listOf(banner1, banner2)
|
|
}
|
|
|
|
@GetMapping("/parallel")
|
|
suspend fun parallel(): List<Banner> = coroutineScope {
|
|
val deferredBanner1: Deferred<Banner> = async {
|
|
client
|
|
.get()
|
|
.uri("/suspend")
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
.awaitExchange()
|
|
.awaitBody<Banner>()
|
|
}
|
|
val deferredBanner2: Deferred<Banner> = async {
|
|
client
|
|
.get()
|
|
.uri("/suspend")
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
.awaitExchange()
|
|
.awaitBody<Banner>()
|
|
}
|
|
listOf(deferredBanner1.await(), deferredBanner2.await())
|
|
}
|
|
|
|
@GetMapping("/error")
|
|
suspend fun error() {
|
|
throw IllegalStateException()
|
|
}
|
|
|
|
@GetMapping("/cancel")
|
|
suspend fun cancel() {
|
|
throw CancellationException()
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
View rendering with a `@Controller` is also supported.
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Controller
|
|
class CoroutinesViewController(banner: Banner) {
|
|
|
|
@GetMapping("/")
|
|
suspend fun render(model: Model): String {
|
|
delay(10)
|
|
model["banner"] = banner
|
|
return "index"
|
|
}
|
|
}
|
|
----
|
|
|
|
|
|
|
|
[[webflux-fn]]
|
|
=== WebFlux.fn
|
|
|
|
Here is an example of Coroutines router defined via the {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL and related handlers.
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Configuration
|
|
class RouterConfiguration {
|
|
|
|
@Bean
|
|
fun mainRouter(userHandler: UserHandler) = coRouter {
|
|
GET("/", userHandler::listView)
|
|
GET("/api/user", userHandler::listApi)
|
|
}
|
|
}
|
|
----
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
class UserHandler(builder: WebClient.Builder) {
|
|
|
|
private val client = builder.baseUrl("...").build()
|
|
|
|
suspend fun listView(request: ServerRequest): ServerResponse =
|
|
ServerResponse.ok().renderAndAwait("users", mapOf("users" to
|
|
client.get().uri("...").awaitExchange().awaitBody<User>()))
|
|
|
|
suspend fun listApi(request: ServerRequest): ServerResponse =
|
|
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyAndAwait(
|
|
client.get().uri("...").awaitExchange().awaitBody<User>())
|
|
}
|
|
----
|
|
|
|
|
|
|
|
[[transactions]]
|
|
=== Transactions
|
|
|
|
Transactions on Coroutines are supported via the programmatic variant of the Reactive
|
|
transaction management provided as of Spring Framework 5.2.
|
|
|
|
For suspending functions, a `TransactionalOperator.executeAndAwait` extension is provided.
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
import org.springframework.transaction.reactive.executeAndAwait
|
|
|
|
class PersonRepository(private val operator: TransactionalOperator) {
|
|
|
|
suspend fun initDatabase() = operator.executeAndAwait {
|
|
insertPerson1()
|
|
insertPerson2()
|
|
}
|
|
|
|
private suspend fun insertPerson1() {
|
|
// INSERT SQL statement
|
|
}
|
|
|
|
private suspend fun insertPerson2() {
|
|
// INSERT SQL statement
|
|
}
|
|
}
|
|
----
|
|
|
|
For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
import org.springframework.transaction.reactive.transactional
|
|
|
|
class PersonRepository(private val operator: TransactionalOperator) {
|
|
|
|
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
|
|
|
|
private fun findPeople(): Flow<Person> {
|
|
// SELECT SQL statement
|
|
}
|
|
|
|
private suspend fun updatePerson(person: Person): Person {
|
|
// UPDATE SQL statement
|
|
}
|
|
}
|
|
----
|
|
|
|
|
|
|
|
|
|
[[kotlin-spring-projects-in-kotlin]]
|
|
== Spring Projects in Kotlin
|
|
|
|
This section provides some specific hints and recommendations worth for developing Spring projects
|
|
in Kotlin.
|
|
|
|
|
|
|
|
[[final-by-default]]
|
|
=== Final by Default
|
|
|
|
By default, https://discuss.kotlinlang.org/t/classes-final-by-default/166[all classes in Kotlin are `final`].
|
|
The `open` modifier on a class is the opposite of Java's `final`: It allows others to inherit from this
|
|
class. This also applies to member functions, in that they need to be marked as `open` to be overridden.
|
|
|
|
While Kotlin's JVM-friendly design is generally frictionless with Spring, this specific Kotlin feature
|
|
can prevent the application from starting, if this fact is not taken into consideration. This is because
|
|
Spring beans (such as `@Configuration` annotated classes which by default need to be extended at runtime for technical
|
|
reasons) are normally proxied by CGLIB. The workaround is to add an `open` keyword on each class and
|
|
member function of Spring beans that are proxied by CGLIB, which can
|
|
quickly become painful and is against the Kotlin principle of keeping code concise and predictable.
|
|
|
|
NOTE: It is also possible to avoid CGLIB proxies for configuration classes by using `@Configuration(proxyBeanMethods = false)`.
|
|
See {api-spring-framework}/context/annotation/Configuration.html#proxyBeanMethods--[`proxyBeanMethods` Javadoc] for more details.
|
|
|
|
Fortunately, Kotlin provides a
|
|
https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin[`kotlin-spring`]
|
|
plugin (a preconfigured version of the `kotlin-allopen` plugin) that automatically opens classes
|
|
and their member functions for types that are annotated or meta-annotated with one of the following
|
|
annotations:
|
|
|
|
* `@Component`
|
|
* `@Async`
|
|
* `@Transactional`
|
|
* `@Cacheable`
|
|
|
|
Meta-annotation support means that types annotated with `@Configuration`, `@Controller`,
|
|
`@RestController`, `@Service`, or `@Repository` are automatically opened since these
|
|
annotations are meta-annotated with `@Component`.
|
|
|
|
https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io] enables
|
|
the `kotlin-spring` plugin by default. So, in practice, you can write your Kotlin beans
|
|
without any additional `open` keyword, as in Java.
|
|
|
|
NOTE: The Kotlin code samples in Spring Framework documentation do not explicitly specify
|
|
`open` on the classes and their member functions. The samples are written for projects
|
|
using the `kotlin-allopen` plugin, since this is the most commonly used setup.
|
|
|
|
|
|
|
|
[[using-immutable-class-instances-for-persistence]]
|
|
=== Using Immutable Class Instances for Persistence
|
|
|
|
In Kotlin, it is convenient and considered to be a best practice to declare read-only properties
|
|
within the primary constructor, as in the following example:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
class Person(val name: String, val age: Int)
|
|
----
|
|
|
|
You can optionally add https://kotlinlang.org/docs/reference/data-classes.html[the `data` keyword]
|
|
to make the compiler automatically derive the following members from all properties declared
|
|
in the primary constructor:
|
|
|
|
* `equals()` and `hashCode()`
|
|
* `toString()` of the form `"User(name=John, age=42)"`
|
|
* `componentN()` functions that correspond to the properties in their order of declaration
|
|
* `copy()` function
|
|
|
|
As the following example shows, this allows for easy changes to individual properties, even if `Person` properties are read-only:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
data class Person(val name: String, val age: Int)
|
|
|
|
val jack = Person(name = "Jack", age = 1)
|
|
val olderJack = jack.copy(age = 2)
|
|
----
|
|
|
|
Common persistence technologies (such as JPA) require a default constructor, preventing this
|
|
kind of design. Fortunately, there is a workaround for this
|
|
https://stackoverflow.com/questions/32038177/kotlin-with-jpa-default-constructor-hell["`default constructor hell`"],
|
|
since Kotlin provides a https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-jpa-compiler-plugin[`kotlin-jpa`]
|
|
plugin that generates synthetic no-arg constructor for classes annotated with JPA annotations.
|
|
|
|
If you need to leverage this kind of mechanism for other persistence technologies, you can configure
|
|
the https://kotlinlang.org/docs/reference/compiler-plugins.html#how-to-use-no-arg-plugin[`kotlin-noarg`]
|
|
plugin.
|
|
|
|
NOTE: As of the Kay release train, Spring Data supports Kotlin immutable class instances and
|
|
does not require the `kotlin-noarg` plugin if the module uses Spring Data object mappings
|
|
(such as MongoDB, Redis, Cassandra, and others).
|
|
|
|
|
|
|
|
[[injecting-dependencies]]
|
|
=== Injecting Dependencies
|
|
|
|
Our recommendation is to try to favor constructor injection with `val` read-only (and
|
|
non-nullable when possible) https://kotlinlang.org/docs/reference/properties.html[properties],
|
|
as the following example shows:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Component
|
|
class YourBean(
|
|
private val mongoTemplate: MongoTemplate,
|
|
private val solrClient: SolrClient
|
|
)
|
|
----
|
|
|
|
NOTE: Classes with a single constructor have their parameters automatically autowired.
|
|
That's why there is no need for an explicit `@Autowired constructor` in the example shown
|
|
above.
|
|
|
|
If you really need to use field injection, you can use the `lateinit var` construct,
|
|
as the following example shows:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Component
|
|
class YourBean {
|
|
|
|
@Autowired
|
|
lateinit var mongoTemplate: MongoTemplate
|
|
|
|
@Autowired
|
|
lateinit var solrClient: SolrClient
|
|
}
|
|
----
|
|
|
|
|
|
|
|
[[injecting-configuration-properties]]
|
|
=== Injecting Configuration Properties
|
|
|
|
In Java, you can inject configuration properties by using annotations (such as pass:q[`@Value("${property}")`)].
|
|
However, in Kotlin, `$` is a reserved character that is used for
|
|
https://kotlinlang.org/docs/reference/idioms.html#string-interpolation[string interpolation].
|
|
|
|
Therefore, if you wish to use the `@Value` annotation in Kotlin, you need to escape the `$`
|
|
character by writing pass:q[`@Value("\${property}")`].
|
|
|
|
NOTE: If you use Spring Boot, you should probably use
|
|
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties[`@ConfigurationProperties`]
|
|
instead of `@Value` annotations.
|
|
|
|
As an alternative, you can customize the property placeholder prefix by declaring the
|
|
following configuration beans:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Bean
|
|
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
|
|
setPlaceholderPrefix("%{")
|
|
}
|
|
----
|
|
|
|
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
|
|
that uses the `${...}` syntax, with configuration beans, as the following example shows:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@Bean
|
|
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
|
|
setPlaceholderPrefix("%{")
|
|
setIgnoreUnresolvablePlaceholders(true)
|
|
}
|
|
|
|
@Bean
|
|
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
|
|
----
|
|
|
|
|
|
|
|
[[checked-exceptions]]
|
|
=== Checked Exceptions
|
|
|
|
Java and https://kotlinlang.org/docs/reference/exceptions.html[Kotlin exception handling]
|
|
are pretty close, with the main difference being that Kotlin treats all exceptions as
|
|
unchecked exceptions. However, when using proxied objects (for example classes or methods
|
|
annotated with `@Transactional`), checked exceptions thrown will be wrapped by default in
|
|
an `UndeclaredThrowableException`.
|
|
|
|
To get the original exception thrown like in Java, methods should be annotated with
|
|
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-throws/index.html[`@Throws`]
|
|
to specify explicitly the checked exceptions thrown (for example `@Throws(IOException::class)`).
|
|
|
|
|
|
|
|
[[annotation-array-attributes]]
|
|
=== Annotation Array Attributes
|
|
|
|
Kotlin annotations are mostly similar to Java annotations, but array attributes (which are
|
|
extensively used in Spring) behave differently. As explained in the
|
|
https://kotlinlang.org/docs/reference/annotations.html[Kotlin documentation] you can omit
|
|
the `value` attribute name, unlike other attributes, and specify it as a `vararg` parameter.
|
|
|
|
To understand what that means, consider `@RequestMapping` (which is one of the most widely
|
|
used Spring annotations) as an example. This Java annotation is declared as follows:
|
|
|
|
[source,java,indent=0]
|
|
----
|
|
public @interface RequestMapping {
|
|
|
|
@AliasFor("path")
|
|
String[] value() default {};
|
|
|
|
@AliasFor("value")
|
|
String[] path() default {};
|
|
|
|
RequestMethod[] method() default {};
|
|
|
|
// ...
|
|
}
|
|
----
|
|
|
|
The typical use case for `@RequestMapping` is to map a handler method to a specific path
|
|
and method. In Java, you can specify a single value for the annotation array attribute,
|
|
and it is automatically converted to an array.
|
|
|
|
That is why one can write
|
|
`@RequestMapping(value = "/toys", method = RequestMethod.GET)` or
|
|
`@RequestMapping(path = "/toys", method = RequestMethod.GET)`.
|
|
|
|
However, in Kotlin, you must write `@RequestMapping("/toys", method = [RequestMethod.GET])`
|
|
or `@RequestMapping(path = ["/toys"], method = [RequestMethod.GET])` (square brackets need
|
|
to be specified with named array attributes).
|
|
|
|
An alternative for this specific `method` attribute (the most common one) is to
|
|
use a shortcut annotation, such as `@GetMapping`, `@PostMapping`, and others.
|
|
|
|
NOTE: If the `@RequestMapping` `method` attribute is not specified, all HTTP methods will
|
|
be matched, not only the `GET` method.
|
|
|
|
|
|
|
|
[[testing]]
|
|
=== Testing
|
|
|
|
This section addresses testing with the combination of Kotlin and Spring Framework.
|
|
The recommended testing framework is https://junit.org/junit5/[JUnit 5] along with
|
|
https://mockk.io/[Mockk] for mocking.
|
|
|
|
NOTE: If you are using Spring Boot, see
|
|
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-kotlin-testing[this related documentation].
|
|
|
|
|
|
[[constructor-injection]]
|
|
==== Constructor injection
|
|
|
|
As described in the <<testing#testcontext-junit-jupiter-di, dedicated section>>,
|
|
JUnit 5 allows constructor injection of beans which is pretty useful with Kotlin
|
|
in order to use `val` instead of `lateinit var`. You can use
|
|
{api-spring-framework}/test/context/TestConstructor.html[`@TestConstructor(autowireMode = AutowireMode.ALL)`]
|
|
to enable autowiring for all parameters.
|
|
|
|
====
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
@TestConstructor(autowireMode = AutowireMode.ALL)
|
|
class OrderServiceIntegrationTests(val orderService: OrderService,
|
|
val customerService: CustomerService) {
|
|
|
|
// tests that use the injected OrderService and CustomerService
|
|
}
|
|
----
|
|
====
|
|
|
|
|
|
[[per_class-lifecycle]]
|
|
==== `PER_CLASS` Lifecycle
|
|
|
|
Kotlin lets you specify meaningful test function names between backticks (```).
|
|
As of JUnit 5, Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
|
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
|
|
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
|
|
|
|
You can also change the default behavior to `PER_CLASS` thanks to a `junit-platform.properties`
|
|
file with a `junit.jupiter.testinstance.lifecycle.default = per_class` property.
|
|
|
|
The following example demonstrates `@BeforeAll` and `@AfterAll` annotations on non-static methods:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
class IntegrationTests {
|
|
|
|
val application = Application(8181)
|
|
val client = WebClient.create("http://localhost:8181")
|
|
|
|
@BeforeAll
|
|
fun beforeAll() {
|
|
application.start()
|
|
}
|
|
|
|
@Test
|
|
fun `Find all users on HTML page`() {
|
|
client.get().uri("/users")
|
|
.accept(TEXT_HTML)
|
|
.retrieve()
|
|
.bodyToMono<String>()
|
|
.test()
|
|
.expectNextMatches { it.contains("Foo") }
|
|
.verifyComplete()
|
|
}
|
|
|
|
@AfterAll
|
|
fun afterAll() {
|
|
application.stop()
|
|
}
|
|
}
|
|
----
|
|
|
|
|
|
[[specification-like-tests]]
|
|
==== Specification-like Tests
|
|
|
|
You can create specification-like tests with JUnit 5 and Kotlin.
|
|
The following example shows how to do so:
|
|
|
|
[source,kotlin,indent=0]
|
|
----
|
|
class SpecificationLikeTests {
|
|
|
|
@Nested
|
|
@DisplayName("a calculator")
|
|
inner class Calculator {
|
|
val calculator = SampleCalculator()
|
|
|
|
@Test
|
|
fun `should return the result of adding the first number to the second number`() {
|
|
val sum = calculator.sum(2, 4)
|
|
assertEquals(6, sum)
|
|
}
|
|
|
|
@Test
|
|
fun `should return the result of subtracting the second number from the first number`() {
|
|
val subtract = calculator.subtract(4, 2)
|
|
assertEquals(2, subtract)
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
|
|
[[kotlin-webtestclient-issue]]
|
|
==== `WebTestClient` Type Inference Issue in Kotlin
|
|
|
|
Due to a https://youtrack.jetbrains.com/issue/KT-5464[type inference issue], you must
|
|
use the Kotlin `expectBody` extension (such as `.expectBody<String>().isEqualTo("toys")`),
|
|
since it provides a workaround for the Kotlin issue with the Java API.
|
|
|
|
See also the related https://jira.spring.io/browse/SPR-16057[SPR-16057] issue.
|
|
|
|
|
|
|
|
|
|
[[kotlin-getting-started]]
|
|
== Getting Started
|
|
|
|
The easiest way to learn how to build a Spring application with Kotlin is to follow
|
|
https://spring.io/guides/tutorials/spring-boot-kotlin/[the dedicated tutorial].
|
|
|
|
|
|
|
|
[[start-spring-io]]
|
|
=== `start.spring.io`
|
|
|
|
The easiest way to start a new Spring Framework project in Kotlin is to create a new Spring
|
|
Boot 2 project on https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
|
|
|
|
|
|
|
[[choosing-the-web-flavor]]
|
|
=== Choosing the Web Flavor
|
|
|
|
Spring Framework now comes with two different web stacks: <<web#mvc, Spring MVC>> and
|
|
<<web-reactive#spring-web-reactive, Spring WebFlux>>.
|
|
|
|
Spring WebFlux is recommended if you want to create applications that will deal with latency,
|
|
long-lived connections, streaming scenarios or if you want to use the web functional
|
|
Kotlin DSL.
|
|
|
|
For other use cases, especially if you are using blocking technologies such as JPA, Spring
|
|
MVC and its annotation-based programming model is the recommended choice.
|
|
|
|
|
|
|
|
|
|
[[kotlin-resources]]
|
|
== Resources
|
|
|
|
We recommend the following resources for people learning how to build applications with
|
|
Kotlin and the Spring Framework:
|
|
|
|
* https://kotlinlang.org/docs/reference/[Kotlin language reference]
|
|
* https://slack.kotlinlang.org/[Kotlin Slack] (with a dedicated #spring channel)
|
|
* https://stackoverflow.com/questions/tagged/spring+kotlin[Stackoverflow, with `spring` and `kotlin` tags]
|
|
* https://play.kotlinlang.org/[Try Kotlin in your browser]
|
|
* https://blog.jetbrains.com/kotlin/[Kotlin blog]
|
|
* https://kotlin.link/[Awesome Kotlin]
|
|
|
|
|
|
|
|
[[examples]]
|
|
=== Examples
|
|
|
|
The following Github projects offer examples that you can learn from and possibly even extend:
|
|
|
|
* https://github.com/sdeleuze/spring-boot-kotlin-demo[spring-boot-kotlin-demo]: Regular Spring Boot and Spring Data JPA project
|
|
* https://github.com/mixitconf/mixit[mixit]: Spring Boot 2, WebFlux, and Reactive Spring Data MongoDB
|
|
* https://github.com/sdeleuze/spring-kotlin-functional[spring-kotlin-functional]: Standalone WebFlux and functional bean definition DSL
|
|
* https://github.com/sdeleuze/spring-kotlin-fullstack[spring-kotlin-fullstack]: WebFlux Kotlin fullstack example with Kotlin2js for frontend instead of JavaScript or TypeScript
|
|
* https://github.com/spring-petclinic/spring-petclinic-kotlin[spring-petclinic-kotlin]: Kotlin version of the Spring PetClinic Sample Application
|
|
* https://github.com/sdeleuze/spring-kotlin-deepdive[spring-kotlin-deepdive]: A step-by-step migration guide for Boot 1.0 and Java to Boot 2.0 and Kotlin
|
|
* https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample[spring-cloud-gcp-kotlin-app-sample]: Spring Boot with Google Cloud Platform Integrations
|
|
|
|
|
|
|
|
[[issues]]
|
|
=== Issues
|
|
|
|
The following list categorizes the pending issues related to Spring and Kotlin support:
|
|
|
|
* Spring Framework
|
|
** https://github.com/spring-projects/spring-framework/issues/20606[Unable to use WebTestClient with mock server in Kotlin]
|
|
** https://github.com/spring-projects/spring-framework/issues/20496[Support null-safety at generics, varargs and array elements level]
|
|
* Kotlin
|
|
** https://youtrack.jetbrains.com/issue/KT-6380[Parent issue for Spring Framework support]
|
|
** https://youtrack.jetbrains.com/issue/KT-5464[Kotlin requires type inference where Java doesn't]
|
|
** https://youtrack.jetbrains.com/issue/KT-20283[Smart cast regression with open classes]
|
|
** https://youtrack.jetbrains.com/issue/KT-14984[Impossible to pass not all SAM argument as function]
|
|
** https://youtrack.jetbrains.com/issue/KT-15125[Support JSR 223 bindings directly via script variables]
|
|
** https://youtrack.jetbrains.com/issue/KT-6653[Kotlin properties do not override Java-style getters and setters]
|