Skip to content

Commit 83eb5bb

Browse files
feat: upgrade to Spring Boot 4.0 with Jackson 3 as the default (#33)
* feat: upgrade to Spring Boot 4.0 with Jackson 3 as the default - Bump to Spring Boot 4.0.6 / Kotlin 2.2.21; Jackson 3 versions come from the BOM. - util: Jackson 3 becomes the runtime default (api), Jackson 2 is compileOnly; createDataSource defaults to the Jackson 3 handler. - spring starter: JsonHandler bean prefers Jackson 3, falls back to Jackson 2 (spring-boot-jackson2); DataMessageMapping doc references tools.jackson. - spring tests use Logback only (drop slf4j-simple) with a quiet logback-test.xml; add kotest SpringExtension + a context test asserting the Jackson 3 handler. - Docs: README compatibility table + MIGRATION.md (2.x/Boot 3.5 -> 3.x/Boot 4.0). Depends on the ChannelSendOperator removal (PR #30) landing on main first. * chore: bump Kotlin JPS plugin to 2.3.20
1 parent 05355d2 commit 83eb5bb

12 files changed

Lines changed: 175 additions & 33 deletions

File tree

.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

AGENTS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Guidance for AI coding assistants working in this repository.
66

77
`DataSource-Extensions` is a multi-module Gradle (Kotlin DSL) library that wraps Caplin's `com.caplin.platform.integration.java:datasource` SDK with modern reactive APIs and a Spring Boot starter. Kotlin Coroutines `Flow` is the canonical internal representation; Java `Flow.Publisher` and Reactive Streams `Publisher` variants are thin adapters over it.
88

9-
JDK 17. Spring Boot pinned to 3.5.x and Kotlin to 2.2.x (see `gradle/libs.versions.toml`). The `common-library` convention plugin applies `io.spring.dependency-management` and overrides Spring's BOM `kotlin.version` to our catalog value — without this, transitive Jackson updates raise `kotlin-stdlib` past what the compiler can read.
9+
JDK 17. Two parallel release lines (see the compatibility table in `README.md`): **`main` targets Spring Boot 4.0.x** (Jackson 3 is the default JSON binding; Jackson 2 is a `compileOnly` opt-in), and **`springboot-3.5.x` is the Spring Boot 3.5.x maintenance branch** (Jackson 2 default). Kotlin pinned to 2.2.21 (see `gradle/libs.versions.toml`). The `common-library` convention plugin applies `io.spring.dependency-management` and overrides Spring's BOM `kotlin.version` to our catalog value — without this, transitive Jackson updates raise `kotlin-stdlib` past what the compiler can read.
10+
11+
`datasourcex-util` ships both Jackson serialization layers under `serialization/{jackson2,jackson3}` (mirrored serializers + a `JsonHandler` each, sharing zjsonpatch for RFC 6902 diff/patch). On `main`, Jackson 3 (`tools.jackson.*`) is the runtime default and Jackson 2 (`com.fasterxml.jackson.*`) is `compileOnly`; on `springboot-3.5.x` it's the reverse. The Spring starter's `JsonHandler` bean auto-selects Jackson 3 when present and falls back to Jackson 2 (`DataSourceAutoConfiguration`).
1012

1113
## Common commands
1214

MIGRATION.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Migration guide
2+
3+
## 2.x (Spring Boot 3.5) → 3.x (Spring Boot 4.0)
4+
5+
Version `3.x` targets **Spring Boot 4.0** and makes **Jackson 3** (`tools.jackson.*`) the default
6+
JSON binding, matching Spring Boot 4's own default. To stay on Spring Boot 3.5, remain on the
7+
[`2.x` line](./README.md#compatibility) (branch `springboot-3.5.x`).
8+
9+
### Prerequisites
10+
11+
- Spring Boot **4.0.x**
12+
- Kotlin **2.2.21+**
13+
- JDK **17+** (unchanged)
14+
15+
### 1. Bump the dependency
16+
17+
```kotlin
18+
dependencies {
19+
implementation("com.caplin.integration.datasourcex:spring-boot-starter-datasource:3.+")
20+
// or, for the reactive / util modules:
21+
implementation("com.caplin.integration.datasourcex:datasourcex-kotlin:3.+")
22+
}
23+
```
24+
25+
### 2. Jackson 3 is now the default
26+
27+
Spring Boot 4 auto-configures a Jackson 3 `tools.jackson.databind.ObjectMapper`, and the starter
28+
wires a Jackson-3-backed `JsonHandler` onto the DataSource by default. For most applications no
29+
change is required — plain POJOs serialize as before.
30+
31+
If you registered **custom Jackson 2 modules, serializers, or `ObjectMapper` customizers**, port them
32+
to Jackson 3. See the
33+
[Jackson 3 release notes](https://github.com/FasterXML/jackson/wiki/Jackson-Release-3.0).
34+
35+
### 3. Keeping Jackson 2 (optional)
36+
37+
To keep using Jackson 2 for DataSource JSON, add Spring Boot's (deprecated) Jackson 2 module and
38+
define your own `JsonHandler` bean. The starter's handler is `@ConditionalOnMissingBean`, so yours
39+
takes precedence:
40+
41+
```kotlin
42+
// build.gradle.kts
43+
implementation("org.springframework.boot:spring-boot-jackson2")
44+
```
45+
46+
```kotlin
47+
import com.caplin.datasource.messaging.json.JsonHandler
48+
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory
49+
import com.fasterxml.jackson.databind.ObjectMapper
50+
import org.springframework.context.annotation.Bean
51+
52+
@Bean
53+
fun dataSourceJsonHandler(objectMapper: ObjectMapper): JsonHandler<*> =
54+
SimpleDataSourceFactory.createJackson2JsonHandler(objectMapper)
55+
```
56+
57+
Outside Spring, `SimpleDataSourceFactory.createDataSource(...)` now defaults to the Jackson 3
58+
handler; pass `SimpleDataSourceFactory.defaultJackson2JsonHandler` (or your own) explicitly to keep
59+
Jackson 2. On the `3.x` line the Jackson 2 artifacts are `compileOnly` in `datasourcex-util`, so add
60+
them to your own classpath if you use the Jackson 2 helpers directly.

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
# Extensions for DataSource
22

3-
Requires Kotlin 2.2 or later.
3+
Requires Kotlin 2.2 or later and JDK 17 or later.
4+
5+
## Compatibility
6+
7+
This library is maintained in two parallel lines — choose the version that matches your Spring Boot
8+
major version:
9+
10+
| Library version | Branch | Spring Boot | Default JSON binding | Kotlin |
11+
|------------------|---------------------|-------------|---------------------------------------------------------------------------------------------------------------|---------|
12+
| `3.x` | `main` | 4.0.x | Jackson 3 (Jackson 2 available via [`spring-boot-jackson2`](https://docs.spring.io/spring-boot/reference/features/json.html)) | 2.2.21+ |
13+
| `2.x` | `springboot-3.5.x` | 3.5.x | Jackson 2 (Jackson 3 available by adding the `tools.jackson` dependencies) | 2.2+ |
14+
15+
Upgrading from `2.x` (Spring Boot 3.5) to `3.x` (Spring Boot 4.0)? See the
16+
[migration guide](./MIGRATION.md).
417

518
## Reactive
619

@@ -32,7 +45,7 @@ Then refer to the documentation:
3245
## Spring
3346

3447
This module provides a starter for integrating Caplin DataSource with your
35-
[Spring Boot 3.5](https://spring.io/projects/spring-boot) application, and integration with
48+
[Spring Boot 4.0](https://spring.io/projects/spring-boot) application, and integration with
3649
[Spring Messaging](https://docs.spring.io/spring-boot/docs/current/reference/html/messaging.html)
3750
for publishing data from annotated functions.
3851

gradle/libs.versions.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
[versions]
2-
springBoot = "3.5.15"
3-
kotlin = "2.2.0"
2+
springBoot = "4.0.6"
3+
kotlin = "2.2.21"
44
kotlinCollectionsImmutable = "0.5.0"
5-
jackson3 = "3.2.0"
65
zjsonpatch = "0.6.2"
76
jmh = "1.37"
87
jmh-plugin = "0.7.3"
@@ -31,8 +30,6 @@ spring-dependency-management-plugin = "1.1.7"
3130

3231
[libraries]
3332
kotlin-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinCollectionsImmutable" }
34-
jackson3-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson3" }
35-
jackson3-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson3" }
3633
zjsonpatch = { module = "io.github.vishwakarma:zjsonpatch", version.ref = "zjsonpatch" }
3734
kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" }
3835
fory-core = { module = "org.apache.fory:fory-core", version.ref = "fory" }

spring/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ dependencies {
1414
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
1515
implementation("org.springframework.boot:spring-boot-starter")
1616
implementation("org.springframework.boot:spring-boot-starter-json")
17-
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
17+
implementation("tools.jackson.module:jackson-module-kotlin")
18+
// Jackson 2 is only needed to compile the jackson2 fallback in DataSourceAutoConfiguration; it is
19+
// present at runtime only if the consumer adds spring-boot-jackson2.
20+
compileOnly("com.fasterxml.jackson.core:jackson-databind")
1821
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
1922
implementation("org.slf4j:slf4j-api")
2023

spring/src/main/kotlin/com/caplin/integration/datasourcex/spring/annotations/DataMessageMapping.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ annotation class DataMessageMapping(
2020
* Methods annotated with this message should return one or more objects to be serialized to
2121
* JSON.
2222
*
23-
* The JSON handler installed in [DataSource] will be used. By default, this is the
24-
* [com.fasterxml.jackson.databind.ObjectMapper] provided by Spring Boot.
23+
* The JSON handler installed in [DataSource] will be used. By default, this is backed by the
24+
* [tools.jackson.databind.ObjectMapper] provided by Spring Boot.
2525
*/
2626
JSON,
2727

spring/src/main/kotlin/com/caplin/integration/datasourcex/spring/internal/DataSourceAutoConfiguration.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@ import com.caplin.integration.datasourcex.util.SimpleDataSourceConfig.Discovery
99
import com.caplin.integration.datasourcex.util.SimpleDataSourceConfig.Peer
1010
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory.createDataSource
1111
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory.createJackson2JsonHandler
12+
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory.createJackson3JsonHandler
1213
import com.caplin.integration.datasourcex.util.getLogger
13-
import com.fasterxml.jackson.databind.ObjectMapper
14+
import com.fasterxml.jackson.databind.ObjectMapper as Jackson2ObjectMapper
1415
import java.nio.file.Paths
1516
import java.util.UUID
1617
import java.util.logging.Logger
1718
import org.springframework.beans.factory.annotation.Value
1819
import org.springframework.boot.autoconfigure.AutoConfiguration
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
1921
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
2022
import org.springframework.boot.context.properties.EnableConfigurationProperties
2123
import org.springframework.context.annotation.Bean
24+
import tools.jackson.databind.ObjectMapper as Jackson3ObjectMapper
2225

2326
@AutoConfiguration
2427
@EnableConfigurationProperties(DataSourceConfigurationProperties::class)
@@ -31,9 +34,20 @@ internal class DataSourceAutoConfiguration {
3134
internal const val DEFAULT_DATASOURCE_NAME = "caplin-adapter"
3235
}
3336

37+
// Prefer Jackson 3 (the Spring Boot 4 default). Falls back to Jackson 2 only when Jackson 3 is
38+
// absent and a Jackson 2 ObjectMapper is present (e.g. the consumer added spring-boot-jackson2).
39+
// Both are @ConditionalOnMissingBean(JsonHandler) so a consumer-defined JsonHandler wins
40+
// outright.
3441
@Bean
35-
@ConditionalOnMissingBean
36-
fun jsonHandler(objectMapper: ObjectMapper): JsonHandler<*> =
42+
@ConditionalOnClass(Jackson3ObjectMapper::class)
43+
@ConditionalOnMissingBean(JsonHandler::class)
44+
fun jackson3JsonHandler(objectMapper: Jackson3ObjectMapper): JsonHandler<*> =
45+
createJackson3JsonHandler(objectMapper)
46+
47+
@Bean
48+
@ConditionalOnClass(Jackson2ObjectMapper::class)
49+
@ConditionalOnMissingBean(JsonHandler::class)
50+
fun jackson2JsonHandler(objectMapper: Jackson2ObjectMapper): JsonHandler<*> =
3751
createJackson2JsonHandler(objectMapper)
3852

3953
@Bean
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.caplin.integration.datasourcex.spring.internal
2+
3+
import com.caplin.datasource.DataSource
4+
import com.caplin.datasource.messaging.json.JsonHandler
5+
import com.caplin.integration.datasourcex.util.serialization.jackson3.Jackson3JsonHandler
6+
import io.kotest.core.spec.style.FunSpec
7+
import io.kotest.extensions.spring.SpringExtension
8+
import io.kotest.matchers.types.shouldBeInstanceOf
9+
import io.mockk.mockk
10+
import org.springframework.beans.factory.annotation.Autowired
11+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
12+
import org.springframework.boot.test.context.SpringBootTest
13+
import org.springframework.context.annotation.Bean
14+
import org.springframework.context.annotation.Configuration
15+
import org.springframework.test.context.TestPropertySource
16+
import tools.jackson.databind.ObjectMapper as Jackson3ObjectMapper
17+
import tools.jackson.databind.json.JsonMapper
18+
19+
/**
20+
* Verifies the autoconfiguration selects the Jackson 3 [JsonHandler] when a Jackson 3
21+
* [ObjectMapper] is present — the Spring Boot 4 default. [ImportAutoConfiguration] loads the
22+
* autoconfiguration with proper ordering so its `@ConditionalOnMissingBean` conditions see the
23+
* test's beans first, letting us mock the DataSource rather than create a real one.
24+
*/
25+
@SpringBootTest(classes = [DataSourceAutoConfigurationTest.TestConfig::class])
26+
@ImportAutoConfiguration(DataSourceAutoConfiguration::class)
27+
@TestPropertySource(properties = ["caplin.datasource.managed.discovery.hostname=localhost"])
28+
class DataSourceAutoConfigurationTest : FunSpec() {
29+
30+
@Autowired private lateinit var jsonHandler: JsonHandler<*>
31+
32+
init {
33+
extension(SpringExtension())
34+
35+
test("wires the Jackson 3 JsonHandler by default") {
36+
jsonHandler.shouldBeInstanceOf<Jackson3JsonHandler>()
37+
}
38+
}
39+
40+
@Configuration(proxyBeanMethods = false)
41+
class TestConfig {
42+
@Bean fun jackson3ObjectMapper(): Jackson3ObjectMapper = JsonMapper.builder().build()
43+
44+
@Bean fun dataSource(): DataSource = mockk(relaxed = true)
45+
}
46+
}

spring/src/test/kotlin/com/caplin/integration/datasourcex/spring/internal/DataSourceEndToEndTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import com.caplin.integration.datasourcex.spring.annotations.DataMessageMapping.
99
import com.caplin.integration.datasourcex.spring.annotations.DataService
1010
import com.caplin.integration.datasourcex.spring.annotations.IngressDestinationVariable
1111
import com.caplin.integration.datasourcex.spring.annotations.IngressToken.USER_ID
12-
import com.fasterxml.jackson.databind.ObjectMapper
13-
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
1412
import io.kotest.assertions.nondeterministic.eventually
1513
import io.kotest.core.spec.style.FunSpec
1614
import io.kotest.extensions.spring.SpringExtension
@@ -29,6 +27,8 @@ import org.springframework.context.annotation.Bean
2927
import org.springframework.context.annotation.Configuration
3028
import org.springframework.messaging.handler.annotation.Payload
3129
import org.springframework.test.context.TestPropertySource
30+
import tools.jackson.databind.ObjectMapper
31+
import tools.jackson.module.kotlin.jacksonMapperBuilder
3232

3333
/**
3434
* End-to-end test of the Spring Boot starter: a real application context wires the
@@ -176,7 +176,7 @@ class DataSourceEndToEndTest : FunSpec() {
176176

177177
@Bean fun dataSource(): DataSource = fake.dataSource
178178

179-
@Bean fun objectMapper(): ObjectMapper = jacksonObjectMapper()
179+
@Bean fun objectMapper(): ObjectMapper = jacksonMapperBuilder().build()
180180
}
181181

182182
private companion object {

0 commit comments

Comments
 (0)