Version 5.0 of Spring introduced the Kotlin DSL (Domain Specific Language) for defining routes and beans.
Using the DSL, you don’t pay the uncertainty of knowing if you wired things correctly.
Did you ever spend hours debugging, just to figure out that you put the bean in a package that is not scanned? Or that you failed some annotation attribute? Or that you are missing some spring-boot-starter-_something
_ in your pom?
Forget about it. If the DSL compiles, then it is sure that spring is resolving your beans!
There exist a second big advantage in using the DSL. From the spring documentation
his mechanism is very efficient, as it does not require any reflection or CGLIB proxies
That’s fantastic. But how efficient? I did some benchmarking, and I’m going to present the results.
Method of evaluation
The source code of the benchmark is here.
I did the test creating two maven modules:
- nodsl: a spring application with classical annotations.
- dsl1: a spring application with the DSL initialization.
The annotation module has the following class hierarchy:
Controller
|__ Service
|__ Repository (using JPA)
The DSL module has instead this other hierarchy:
Router
|__ Handler
|__ Repository (without JPA)
I created an annotation processor with Kapt to replicate these structures. I did the test with 1000 replicas of each structure. Every replica has its own package.

The controller’s hierarchy is registered in the Spring context, with the typical annotations @RestController, @Service, @Repository, @Entity, @Component
. The router’s hierarchy is registered as follows:
@HowManyRoutes(1000)
@SpringBootApplication(scanBasePackages = [])
class DslApplication
fun main(args: Array<String>) {
runApplication<DslApplication>(*args){
addInitializers(allBeans())
}
}
fun allBeans(): BeanDefinitionDsl {
return beans {
beans1(this)
beans2(this)
...
beans1000(this)
}
}
public fun beans1(dsl: BeanDefinitionDsl): Unit {
dsl.bean<HelloHandler1>()
dsl.bean<InMemoryCustomerRepository1>()
dsl.bean(::helloRouter1)
}
public fun helloRouter1(handler: HelloHandler1) = router {
"hello1".nest {
GET("", handler::hello)
}
}
Results
You can find the logs of the application start, in the root of the repository:
This is the result of the first run:

At a first sight, it looks very promising. The difference is significant both in the initialization of the context and the total start time.
I have to point out that with the DSL I’m not using JPA. The repository scanning takes up to 22.185 seconds, in the "no DSL" case. I’m not creating the tables in the database during the startup (spring.jpa.hibernate.ddl-auto=none
)
What happens then, if I remove JPA while preserving the dependency structure? I substituted the @Repository
with a normal @Component
and removed spring-boot-starter-data-jpa
. You can find the code in the branch no-jpa
of the repository on Github.

These results surprised me. Almost no difference in using the DSL or not, if JPA is not in place. There exist a little difference in the time needed to initialize the context, but the total time to start is pretty much the same.
Discussion

I started this benchmark by asking how much the DSL would have improved the boot time of a Springboot application. After the benchmark, I can say that there is no direct benefit in the DSL itself.
JPA uses CGLIB code generation, and this benchmark proves that it has a huge impact on boot performance. Also using the @Transanctional
annotation results in wrapping your bean in a CGLIB proxy.
If you want to use Springboot and maintaining acceptable boot time, then you should avoid Spring proxying your beans with CGLIB. This means avoiding JPA, @Transactional
annotation, and also @Bean
factory method declared inside a @Configuration
.
I didn’t like JPA even before this benchmark. Now I have one more reason for avoiding it.
I have in my personal backlog to give a try of Kotlin Exposed. This is a good candidate for substituting JPA and Hibernate. Exposed, goes in the same direction as the Spring DSL of avoiding "Convention over configuration".
Conclusion
There is no real benefit in the DSL, concerning the time of scanning and of injecting beans. Anyway, the premise was that it improves performance because it doesn’t use CGLIB and the reflection.
With this benchmark, I proved how huge is the impact of CGLIB. Then, using a coding style that enforces you to avoid it, has the indirect benefit of improving the boot start time.
"Convention over configuration" sounds like charming and magic when you try it the first time. Unfortunately, it can become quickly like a curse, when you have to debug complex situations.
Going for a declarative approach can save you hours of debugging. Having the compiler as a coding companion, lets you feel much more confident of what you are writing. This is an advantage also because there is much less chance of using a tool in the wrong way.
I’m pretty sure that for my next project I’m going to try the DSL, and will avoid JPA and Hibernate.
Let me know your feeling about abandoning JPA, Hibernate and "Convention over configuration". Does it scare you, or it makes you excited?
Thank you for reading.