About ± once a year, there is a desire to see: if I start the application from scratch now, what would I definitely include in it? Besides the obvious practical value (usually a new project starts) it also allows you to realize what architectural experience has been gained recently.

The need for a template over https://start.spring.io caused by the following types of improvements:

  1. The choice of technologies. Yes, you always try to choose something consciously,
    but, in general, many options will work. Nevertheless, you need to choose something. As from the set start.spring.io , and additionally. There are technologies both basic and for improving the development experience (auxiliary).
  2. It is necessary to link all the components together. This is exactly what Spring Boot is dedicated to and start.spring.io . However, it will be clear from the code that this is not enough and you still need to write your own bundles.
  3. The best default values: for example, it is unclear in which universe, by default, it is necessary to terminate immediately, rather than gradually complete processing requests. This is one of the most striking, but the parameters have been changed quite a lot.
  4. Examples: It’s good to see basic examples of technology usage right away. You can delete them almost immediately from the real application, however, they will remain in the template repository. This set of best practices shows how you usually need to encode a particular case.

The difference between https://start.spring.io and what happened after manual revision can be easily tracked through the history of the comits.

The template is also good because it makes it quite easy to answer the question “what if we use XXX technology?” For example, “what if you use the DGS library for GraphQL?” The template is small and public, but there is code that implements typical endpoints – you can experiment without being distracted by unimportant details in the experiment.

So if there are suggestions for improvement – fork & commit. Show your kung fu :).

Next, architectural solutions will be presented in separate sections. This is a brief discussion of a topic, the decision made and what alternatives were considered. On most topics in my blog there are separate notes with more detailed arguments, if you are suddenly interested (or even it turns out to be very long). At the end there is a link to the repository with an example code for these solutions.

Language: Kotlin

Options: Java, Kotlin, Rails, Python, Golang, Node.js

Kotlin shows the best balance between the ease and readability of the code and the speed and stability of the platform. There are several articles on the blog that have been visited in more detail on this topic.

Framework: Spring Boot

Options: Spring Boot, Quarkus.

Quarkus provides the best user experience for developers (quick build, case guides, easy native build support).

At the same time, unfortunately, Quarkus lags behind in support of asynchrony in general and for Kotlin (coroutines) in particular (for example, in GraphQL). And Spring Boot has adopted key innovations (http clients via interfaces and native binaries).

Given the greater popularity among developers, Spring Boot looks like a good option.

Style: asynchronous Kotlin co-routines

Options: Synchronous, Asynchronous Reactor, asynchronous Kotlin co-routines

Now it makes no sense to start applications in a synchronous style, because asynchronous has quite obvious advantages and is already well supported by languages and frameworks. Coroutines are much more readable than the Rx style, which is why we use them.

DB: Postgres

Options: Postgres, MySQL, Oracle, MongoDB

Postgres as the default solution. For more details, see one of the blog posts.

SQL API: Spring Data and jOOQ

Options: [Hibernate](https://hibernate.org/orm /) (aka JPA), [MyBatis](https://mybatis.org/mybatis-3 /), [EclipseLink](https://www.eclipse.org/eclipselink /) , [querydsl](http://querydsl.com /), JetBrains Exposed, Panache, [jOOQ](https://www.jooq.org /), Spring Data R2DBC

In general, all options are bad. Perhaps the topic is too complicated.

jOOQ looks good, but there is a feeling that there are not enough resources or an alternative development path – it is difficult for simple cases.

Spring Data is well suited for simple queries.

Read more on the blog in the specialized notes “Choosing an ORM for Kotlin” and“Features of jOOQ”.

DB migrations: in-app SQL files via Flyway

Options: in-app, external; Flyway, Liquibase

Based on the principles of CICD, migrations must be inside the application in order to be applied automatically.

Flyway is good at supporting sql files. Cancellations are not necessary in principle - migrations are only going forward. If you need to roll back for development, then you can recreate the corresponding database from scratch.

Build System: Gradle Kotlin

Options: Maven, Gradle, Gradle with Kotlin

It doesn’t really matter, but it’s easier to add custom functionality to Gradle, which is sometimes needed.

If we use Kotlin, then why not use it in Gradle (so that we don’t use/learn another language just for the sake of Gradle).

Version control system: Git

Options: CVS, SVN, Git, Perforce, Mercurial, ClearCase, Team Foundation.

Usually everyone is satisfied with Git. You can find fault with the details and, hopefully, one day they will improve it (especially in the ways to connect one repository to another), but there is no point in changing the system itself for this.

If someone is using something else now, then it has “historically developed” and it is too expensive to change.

UI of the version control system: Gitea

Options: Microsoft GitHub, Gitlab, Gitea, Atlassian Bitbucket, JetBrains Space

There was a serious choice between 2 Open Source versions: Gitlab and Gitea, because the other options are expensive for self hosting, and they do not provide enough functions relative to the free options.

Gitlab is more advanced, but Gitea is quickly catching up with it and is already a very pleasant product. At the same time, Gitlab is a commercial development and interesting features are included in the paid versions, which is not the case with Gitea. And it’s also important – Gitea is written in Golang, and Gitlab is written in Rails. Because of this, Gitea is faster and has fewer system requirements.

CICD engine: Gitea

Options: Drone, ArgoCD, Gitea

Gitea has an engine similar to GitHub/Gitlab, so there’s no point in looking for something else. If you don’t like it due to current limitations (this subsystem appeared relatively recently and is actively developing in Gitea), then you can temporarily use something else (Drone).

Code Quality Analyzer: SonarQube, Ktlint

Options: detekt, ktlint, sonarqube

sonarqube finds a lot of problems qualitatively. ktlint is more in formatting, but it also finds well and is used.

detekt finds too many questionable things by default. I’m still looking at it, it’s not in the template yet.

Editor: Idea Ultimate Edition

Options: Idea SE, Idea UE, VS Code

So far, Idea UE is out of competition, but there is hope for VS Code in a few more years of development.

Basic additional plugins:

  • Kotlin Fill Class
  • SonarLint
  • GraphQL
  • .ignore
  • EnvFile
  • Kubernetes

API Technology: GraphQL

Options: plain HTTP, REST, jRPC, GraphQL

GraphQL has good typing and popularity. The main difference from HTTP/REST is the introduction of a new layer of abstraction – requests. It does not introduce a noticeable slowdown initially, but only manifests itself even better with the development of the project:

  • you can collect all the queries to understand whether something is being used or not
  • clients (UI) can update their requests without changing the server, which leads to independence and less technical debt



GraphQL itself spurs the CQRS style: separate queries and mutations. Again, over time, the benefits of this approach only increase.

Application Deployment: Kubernetes or Podman

Options: Kubermetes, Podman, Docker compose, Docker, Systemd service

If the infrastructure already has Kubernetes, then the obvious option is to continue using it. If not (for example, at home), then you can use Podman – it also allows you to create podas similar to Kubernetes, but without all the heavy strapping.

For more information, see the specialized blog post “How to run server applications in 2023”.

Bundling the application: Docker

Options: zip, war, jar, rpm, deb, docker

Since it will be launched either through Docker or Kubernetes, then docker is the obvious answer.

Build via Jib, because it does not require Docker (convenient for CICD).

Basic versions

  • JVM: 17 – current stable
  • Kotlin: 1.8 – current version from the application generator
  • Spring Boot 3 (Spring 6) – current stable
  • Postgres: 15.2 – current stable

Architectural principle: monolith

Options: monolith, microservice

You should always start with a monolith, because this way it is faster to write and correct. Sometime later, this can be divided into microservices.

It is clear that if you have an S3 API at your disposal, then you do not need to write it yourself. You can use this as an external microservice.

Similarly, if some code is written in another language, you don’t need to worry too much, you can wrap it in a separate microservice.

At the same time, it makes sense to leave the main code for the first years of development in the monolith. And then it will be better to understand whether it is necessary to move away from this and, if necessary, how.

For example, the Gitea project is a monolith and it is unlikely that it will ever become a microservice.

Modularity: one module

Options: one module, many modules

I have not seen any advantages from the experience of working with multimodule projects. It’s just a problem. Therefore, one module is enough.

Principle: Folder structure

Main folders:

  • bin – scripts used for development
  • docs – the main documentation (then you can submit it to the wiki if the documentation is actively developed)
  • deployment – files related to deployment in Kubernetes or Podman
  • src/main/resources/db/migration – migrations of the Flyway database schema

Principle: Package structure

Options: quite a lot

Basic Packages:

  • client – clients to external systems (usually http clients, but there may be queues or something else)
  • config – application, Spring and library settings
  • db – code for working with Postgres
  • dao – jOOQ: manual code for communicating with Postgres
  • entity – Spring Data
  • repository – Spring Data
    • sql – jOOQ: generated Postgres schema code
  • domain – domain code
  • “domain name” – in the application (because it is a monolith, most likely there will be several domains) - client – adapter to external clients in terms of domain models
  • model – domain models - service – domain services
  • graphql – code specific to GraphQL
  • service – application-level services
  • utils – classes that in an ideal world should be in some libraries
  • web – HTTP-specific code

In general, the structure implies a clear division of the code into levels - working with the external environment, technical code, business code, taking into account the usual specifics of applications.

Unlike more stringent policies, it assumes the possibility of penetration of data structures between layers. This is done for practical reasons: it practically does not happen that the client has completely changed, but the data structure has remained the same in the end. Even if this happens, automatic refactoring fixes it all. Purism for the sake of purism leads to unnecessary code, which increases the cost of project development and support (because this is a very important topic, there is a separate blog post about it).

You can also notice that the package structure does not imply packages for interfaces. This is due to the fact that interfaces are almost not expected. An interface has the right to live in this structure if it already has several implementations (the same principle of the absence of unnecessary code).

You can test the package structure using [ArchUnit](https://www.archunit.org /). There is no example in this starter, but maybe someday it will appear (I use it on some projects, but I can’t say that I like the year, and the value of automatic verification is only in really large teams).

It is assumed that there will be 3-10 files at each level. If more or less, then, ideally, you need to adjust the package structure.

Principle: no package with the application name

Options: with a package with the name of the application, without a package with the name of the application

For example, let the organization’s package be name.stepin, and let the application be called `kotlin-bootstrap-app’. Then there are 2 following basic package options for the application:

  • name.stepin
  • name.stepin.kotlin-bootstrap-app

The second option looks less successful:

  • this makes it more difficult to move classes from application to application, and we do not plan to put all the sources in one file tree
  • the path is limited to 255 and we used 21 characters from scratch, as a result, it may not be enough somewhere later

The principle of versioning: semantic versions

Variants: semantic versions, many others

For server applications, it is quite possible to use semantic versions, where the first digit is whether compatibility with old clients is broken or not (which corresponds to semantic versions).

For more information, see the specialized blog post “Semantic versions”.

Additional libraries: MapStruct? No

Options: write yourself, MapStruct

Kotlin is well suited to write this himself.

For more information, see the specialized blog post “How I didn’t make KGenMapper”.

Additional libraries: hibernate validator

This is a library for writing validators. Usually it is needed and it is enough. I was not looking for alternatives.

Additional libraries: mockk

This is a good library with full Kotlin support. There is also integration with Quarkus. There are no special alternatives.

Test coverage assembly: kover

Options: jacoco, kover

JaCoCo is essentially a de facto standard, but it does not support some specific Kotlin designs, so it is better to use Kover.


The principles of logging are described in a specialized blog post.

The log4j-api-kotlin library is used for logging, because it allows you to conveniently use logs:

companion object : Logging

logger.info { "hello $arg" }

Kotlin starter

To start the Quarkus version of the template, [the following was used starter](https://code .quarkus.io/?g=name.stepin&a=kotlin-bootstrap-app&b=GRADLE_KOTLIN_DSL&e=kotlin&e=reactive-pg-client&e=flyway&e=agroal&e=config-yaml&e=smallrye-graphql&e=smallrye-health&e=jdbc-postgresql&e=hibernate-validator&e=smallrye-openapi&e=jacoco&e=io.quarkiverse.jooq%3Aquarkus-jooq&e=micrometer&e=micrometer-registry-prometheus&e=resteasy-reactive-jsonb&e=resteasy-reactive-kotlin-serialization&e=resteasy-reactive-jackson&e=resteasy-reactive&e=rest-client-reactive-jsonb&e=rest-client-reactive-jackson&e=rest-client-reactive):

  • Kotlin
  • YAML Configuration
  • JDBC Driver - PostgreSQL
  • Agroal - Database connection pool
  • Flyway
  • Quarkus jOOQ
  • RESTEasy Reactive
  • RESTEasy Reactive Jackson
  • REST Client Reactive
  • REST Client Reactive Jackson
  • Hibernate Validator
  • SmallRye Health
  • SmallRye GraphQL
  • SmallRye OpenAPI
  • Micrometer metrics
  • Micrometer Registry Prometheus
  • Jacoco - Code Coverage

Almost all the components are disassembled above in the architectural solutions. If not sorted out, then it seemed too boring/obvious to me.

Then there were several changes/additions, this can be seen in the history of git.

It so happened that at first I did it on Quarkus, I decided to save that version as well, a lot has been done there.


A repository with the code. Variations:

There are several more solutions in the code than described here – and how to work with dependencies (through the constructor), and how to write tests.


The video shows a lower-level (file-by-file) analysis of the template: