This is the official testing laboratory for Ghost Serialization in Spring Boot environments. It serves as both a performance validation tool and a blueprint for production-grade backend integrations.
Ghost version: 1.2.0 from Maven Central (com.ghostserializer). Clone and build β no local checkout of ghost-serializer required.
Related projects:
| Project | Description |
|---|---|
| ghost-serializer | Main library, KMP sample app, JVM benchmarks |
| ghost-android-test-app | On-device Android benchmark vs Gson, Moshi, KSer |
| ghost-ios-test-app | Native iOS benchmark vs Apple Codable (XCFramework bundled) |
Requirements: Java 17+, Python 3 (for the automated script).
./gradlew bootRun --refresh-dependenciesOpens http://localhost:8081. Use the UI to compare Ghost vs Jackson on the same endpoints.
# Terminal 1
./gradlew bootRun
# Terminal 2
python3 benchmark.pyCoordinates: Maven artifacts use
com.ghostserializer. Kotlin packages usecom.ghost.serialization.
| Artifact | Purpose |
|---|---|
com.ghostserializer:ghost-api |
Annotations (@GhostSerialization, etc.) |
com.ghostserializer:ghost-serialization |
Runtime engine |
com.ghostserializer:ghost-compiler |
KSP code generator |
com.ghostserializer:ghost-spring-boot-starter |
Spring MVC + WebFlux codecs |
com.ghostserializer.ghost (Gradle plugin) |
Auto-wires KSP + dependencies |
# gradle/libs.versions.toml
[versions]
ghost = "1.2.0"
[libraries]
ghost-spring-boot-starter = { group = "com.ghostserializer", name = "ghost-spring-boot-starter", version.ref = "ghost" }
[plugins]
ghost = { id = "com.ghostserializer.ghost", version.ref = "ghost" }// build.gradle.kts
plugins {
alias(libs.plugins.ghost)
// kotlin-jvm, spring-boot, ksp, etc.
}
ghost {
version.set(libs.versions.ghost.get())
}
dependencies {
implementation(libs.ghost.spring.boot.starter)
}
ksp {
arg("ghost.moduleName", "your_app") // e.g. benchmark_app β GhostModuleRegistry_your_app
}// settings.gradle.kts
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
}
}import com.ghost.serialization.annotations.GhostSerialization
@GhostSerialization
data class UserResponse(val id: Long, val name: String)@RestController
class UserController {
@PostMapping("/users")
fun create(@RequestBody body: CreateUserRequest): UserResponse = /* ... */
@GetMapping("/users/{id}")
fun get(@PathVariable id: Long): UserResponse = /* ... */
}You can use @GhostStrict and @GhostCoerce at the class (Controller), method (Endpoint), or parameter (@RequestBody) levels:
import com.ghost.serialization.annotations.GhostStrict
import com.ghost.serialization.annotations.GhostCoerce
@RestController
@RequestMapping("/users")
class UserController {
// 1. Strict Mode: Forces strict comma/syntax validation for this request payload
@PostMapping("/strict")
fun createStrict(
@RequestBody @GhostStrict request: CreateUserRequest
): UserResponse = /* ... */
// 2. Coerce Mode: Automatically coerces stringified inputs to numbers/booleans for this entire endpoint
@GhostCoerce
@PostMapping("/coerce")
fun createCoerced(
@RequestBody request: CreateUserRequest
): UserResponse = /* ... */
}No manual Ghost.addRegistry() in main() β on JVM, KSP writes META-INF/services/com.ghost.serialization.contract.GhostRegistry and Ghost discovers your module at runtime via ServiceLoader.
Optional cold-start tuning only:
fun main(args: Array<String>) {
Ghost.prewarm() // optional: warms serializer cache before first request
runApplication<YourApplication>(*args)
}| Responsibility |
|---|
Adds ghost-api, ghost-serialization, ghost-compiler (KSP) |
Runs KSP at compile time β YourModelSerializer.kt per @GhostSerialization class |
Generates GhostModuleRegistry_<moduleName> + ServiceLoader registration file |
| No Spring wiring β only serialization codegen and runtime deps |
Auto-configures GhostAutoConfiguration when Spring Boot starts:
| Stack | What gets registered | Effect |
|---|---|---|
| Spring MVC (Servlet) | GhostHttpMessageConverter at index 0 |
@RequestBody / @ResponseBody with application/json use Ghost when the type has a generated serializer |
| Spring WebFlux (Reactive) | GhostReactiveDecoder + GhostReactiveEncoder |
Same for reactive controllers (Mono, Flux, codec pipeline) |
Read path: HTTP body β ByteArray β pooled GhostJsonReader (no extra Okio/stream layers).
Write path: pooled GhostJsonFlatWriter β ByteArray β response body in one write.
Type routing: A type is handled by Ghost only if Ghost.getSerializer(clazz) != null (i.e. @GhostSerialization + KSP). Everything else stays on Jackson / default codecs β you can mix both in the same app.
What the starter does not do:
- Does not generate serializers (that's KSP + plugin).
- Does not register your
GhostModuleRegistry(that's ServiceLoader from KSP). - Does not remove Jackson β Jackson remains for non-Ghost types and for this benchmark's comparison endpoints.
- WebFlux + starter β Ghost codecs on the reactive stack.
- Benchmark controller calls
Ghost.deserialize/Ghost.encodeToBytesdirectly and compares with Jackson on the same payloads (~10kGhostCharacterrecords, ~5.6 MB JSON). Ghost.prewarm()inmain()is optional here to reduce first-hit latency during demos; not required for correctness.
Methodology:
benchmark.py, 16 concurrent workers, 10k requests per engine/op/mode. Avg Memory (Waste) =ThreadMXBean.getThreadAllocatedBytes()delta on the request thread (bytes allocated during the call, not heap retained).Ghost WRITE / ByteArray: Measures
Ghost.encodeToBytes(includesFlatByteArrayWritergrowth +copyOfresult). With Ghost 1.2.0, JVM keeps the writer buffer warm up to 8 MB (GhostHeuristics.maxWarmWriteBufferCapacity).Numbers below are from a 1.2.0 run on a production build.
| Engine | Operation | Mode | Avg latency | Avg memory | Throughput |
|---|---|---|---|---|---|
| Jackson | Write | String | 10.45 ms | 22764 KB | 1528 ops/s |
| Ghost | Write | String | 6.46 ms | 5907 KB | 2465 ops/s |
| Jackson | Write | Bytes | 8.35 ms | 11403 KB | 1906 ops/s |
| Ghost | Write | Bytes | 5.88 ms | 5867 KB | 2707 ops/s |
| Jackson | Read | String | 31.91 ms | 34461 KB | 500 ops/s |
| Ghost | Read | String | 10.79 ms | 3513 KB | 1476 ops/s |
| Jackson | Read | Bytes | 31.22 ms | 34460 KB | 511 ops/s |
| Ghost | Read | Bytes | 10.30 ms | 3513 KB | 1544 ops/s |
- Reads ~3.5Γ faster than Jackson on this payload.
- ~10Γ less allocation on reads (~3.5 MB vs ~34 MB per request).
- Spring Boot API unchanged: starter + annotated models + plugin.
Full docs: ghost-serializer β Spring Boot.
| Context | Best for demonstrating |
|---|---|
| Android test app | GC/jank, Retrofit/Ktor, R8-safe serializers |
| iOS test app | Codable alternative, XCFramework |
| This app | Jackson on large JVM payloads and WebFlux throughput |
Plugin 1.2.0 not found: Sonatype can show PUBLISHED before repo.maven.apache.org syncs. Verify on Maven:
curl -s https://repo.maven.apache.org/maven2/com/ghostserializer/ghost/com.ghostserializer.ghost.gradle.plugin/maven-metadata.xml | grep 1.2.0Then: ./gradlew --stop && ./gradlew bootRun --refresh-dependencies.
Ghost NOT_FOUND at runtime: Model missing @GhostSerialization, KSP not applied, or ghost.moduleName mismatch β rebuild after fixing ksp { arg("ghost.moduleName", "...") }.
Port 8081 in use: Change server.port in application.properties or stop the other process.
Part of the Ghost Serialization ecosystem. π»