Showing posts with label spring boot. Show all posts
Showing posts with label spring boot. Show all posts

Monday, April 13, 2026

Alternatives for LocalStack in TestContainers with Spring Boot

Introduction

Since around 20 March 2026 the company behind LocalStack decided to not support the community edition of LocalStack anymore, only paid versions are now available. 


A lot of forks were made from that now-frozen community repository, like this one.  Will any of these become the new community supported version?

Many teams use LocalStack within TestContainers in a Spring Boot application for example. What are the alternatives? This post is about exploring those.

Note that you can also pin the version of LocalStack to the last community edition release 4.14.0, that will keep on working after 6 april 2026. But there won’t be any updates on it of course…

Two forms are emerging: either replace the whole AWS stack as much as possible with one framework, or take an separate implementation per AWS service, so: implementation A to support one specific AWS service and implementation B for another AWS service, etc.

Below I’ve split my analysis in those two forms. 



Full AWS stack replacements


Several options exist for full AWS stack replacements.

Floci

 

Floci is a free, open-source local AWS emulator which can be found at https://github.com/floci-io/floci
It is a very recently created project , but got quite some traction already.  
A week ago Floci changed into a separate organisation, see this LinkedIn post
It is not a ful drop-in replacement in TestContainers, but it comes close. Below shows what needs changing in a Kotlin Spring Boot application if you are currently using LocalStack.

Replacing LocalStack 

 

You probably have something like this currently configured with LocalStack:

@TestConfiguration

class TestConfig {

  @Bean

  fun localStackContainer(): LocalStackContainer {

    return LocalStackContainer(DockerImageName.parse("localstack/localstack:4:14:0"))

      .withServices(LocalStackContainer.Service.DYNAMODB)

  }


Basically changing it by replacing the docker image name and the environment variables should get it working already you’d think, so like this:

@TestConfiguration

class TestConfig {

@Bean

fun localStackContainer(): LocalStackContainer {

  return LocalStackContainer(DockerImageName.parse(hectorvent/floci:1.5.2).asCompatibleSubstituteFor("localstack/localstack"))

    .withEnv("AWS_ENDPOINT_URL", "http://localhost:4566")

    .withEnv("AWS_DEFAULT_REGION", "us-east-1")

    .withEnv("AWS_ACCESS_KEY_ID", "test")

    .withEnv("AWS_SECRET_ACCESS_KEY", "test")

}


But no it is not that easy. You’ll need to insert a different startup-script.
For example like this:

class FlociContainer(dockerImageName: DockerImageName) :   LocalStackContainer(dockerImageName) {


override fun containerIsStarting(containerInfo: InspectContainerResponse) {

  var shell: String

  var executable: String

  if (dockerImageName.contains("jvm")) {

    shell = "/bin/sh"

    executable = "java -jar quarkus-app/quarkus-run.jar"

  } else {

    shell = "/bin/bash"

    executable = "./application"

  }

  var command = shell + "\n" + executable + "\n"

  try {

    copyFileToContainer(Transferable.of(command, 511), STARTER_SCRIPT)

  } catch (e: Throwable) {

    logger.error("Failed to copy startup script to container: ${e.message}", e)

    throw e

  }

}

}

 

Note the logic to determine the correct command for the startup-script to use in the Docker image. Floci has a native version which has a different startup-script than the JVM version.
You’ll also need to parse for a different startup string than LocalStack has.
TestContainers has a module for LocalStack that looks for this string: ".*Ready\\.\n"
But Floci does not log that string, so one to search for is: ".*started in.*"
So you need to override that for the Floci version. 


Then the test configuration becomes this:

@TestConfiguration

class TestConfig {

@Bean

fun localStackContainer(): LocalStackContainer {

  val flociImage: DockerImageName =

  DockerImageName.parse(hectorvent/floci:1.5.2).asCompatibleSubstituteFor("localstack/localstack")

 

  return FlociContainer(flociImage)

          // Floci settings

    .withEnv("AWS_ENDPOINT_URL", "http://localhost:4566")

    .withEnv("AWS_DEFAULT_REGION", "us-east-1")

    .withEnv("AWS_ACCESS_KEY_ID", "test")

    .withEnv("AWS_SECRET_ACCESS_KEY", "test")

          // Disable services you don’t need

    .withEnv("FLOCI_SERVICES_SSM_ENABLED", "false")

    .withEnv("FLOCI_SERVICES_ELASTICSEARCH_ENABLED", "false")

           // Floci container has different log-text to look for

    .waitingFor(LogMessageWaitStrategy().withRegEx(".*started in.*").withTimes(1))

}

 

That should do it!


Building Docker JVM image of Floci and start it locally 

 

For building a Docker image for the JVM version, you can run these commands in the root directory of the Floci project: 

  1. Create the artifact (application) for the Docker image: ./mvnw clean package
  2. Build the docker image: docker build . -t floci-jvm-package:1.5.1-fix-001 -f Dockerfile.jvm-package 


To start the JVM image of Floci locally you can run this command in the root directory:

 

docker-compose -f docker-compose-fix-001.xml up

 

With this in that docker compose file:

 

services:

    floci:

        image:    floci-jvm-package:1.5.1-fix-001

        ports:

            - "4566:4566"

        volumes:

            - ./data:/app/data

            - ./init/start.d:/etc/floci/init/start.d:ro

            - ./init/stop.d:/etc/floci/init/stop.d:ro


The output will look something like this then:

Container common-adapters-floci-1    Recreated                                                                                                                                                                                                                                                                                                                          0.1s

Attaching to floci-1

floci-1    |      ______ _            ____      _____ _____

floci-1    |    |    ____| |        / __ \ / ____|_      _|

floci-1    |    | |__    | |      | |    | | |            | |   

floci-1    |    |    __| | |      | |    | | |            | |   

floci-1    |    | |        | |___| |__| | |____ _| |_

floci-1    |    |_|        |______\____/ \_____|_____|

floci-1    |

floci-1    |

floci-1    |                      Powered by Quarkus 3.32.3

floci-1    | 2026-04-11 15:51:14,012 INFO    [io.github.hectorvent.floci.lifecycle.EmulatorLifecycle] (main) === AWS Local Emulator Starting ===

floci-1    | 2026-04-11 15:51:14,012 INFO    [io.github.hectorvent.floci.lifecycle.EmulatorLifecycle] (main) Storage mode: memory

floci-1    | 2026-04-11 15:51:14,012 INFO    [io.github.hectorvent.floci.lifecycle.EmulatorLifecycle] (main) Persistent path: ./data

floci-1    | 2026-04-11 15:51:14,012 INFO    [io.github.hectorvent.floci.core.common.ServiceRegistry] (main) Enabled services: [ssm, sqs, s3, dynamodb, sns, lambda, apigateway, iam, elasticache, rds, events, scheduler, logs, monitoring, secretsmanager, apigatewayv2, kinesis, kms, cognito-idp, states, cloudformation, acm, email, es]

floci-1    | 2026-04-11 15:51:14,013 INFO    [io.github.hectorvent.floci.lifecycle.EmulatorLifecycle] (main) === AWS Local Emulator Ready ===

floci-1    | 2026-04-11 15:51:14,021 INFO    [io.quarkus] (main) floci 1.4.0 native (powered by Quarkus 3.32.3) started in 0.085s. Listening on: http://0.0.0.0:4566

floci-1    | 2026-04-11 15:51:14,021 INFO    [io.quarkus] (main) Profile prod activated.

floci-1    | 2026-04-11 15:51:14,021 INFO    [io.quarkus] (main) Installed features: [cdi, config-yaml, rest, rest-jackson, smallrye-context-propagation, vertx]


Notice also the ‘started in 0.085s’, which contains the string for which the above example FlociContainer is looking for to see if the container started successfully.


Automatically create your application queues in Floci


A fancy enhancement for when you are using Spring Boot: you can write a BeanFactoryPostProcessor which parses for @SqsListener annotations to automatically create the queues in the Floci container.

Run Integration Tests against locally running Floci


To run your tests against a locally running Floci instance directly, for example to see its logging, started up with Docker like mentioned above, you override the endpoint when creating the DynamoDBClient:

    return DynamoDbClient.builder()

          .endpointOverride(URI.create("http://localhost:4566"))

    .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(localStackContainer.accessKey, localStackContainer.secretKey)))

    .region(Region.of(localStackContainer.region))

    .build()

}


Testcontainers module for Floci

 

There’s now also a testcontainers module available for floci so you should not need all above manual changes: https://testcontainers.com/modules/floci/

I did not test this module yet.

 



Other full replacement options

  • Rust focused it seems: Rustack
  • Only 1 maintainer and runs standalone, some AWS, some GCP, some Azure services supported: CloudTwin
  • Python focused it seems: Moto: This post talks about Moto mocking and using it in unittests. Is it a mocking or emulating framework?
  • Any of the LocalStack forks.


AWS stack per-service replacement


Multiple options exist, this list might not be listing all options. 

  1. DynamoDB: 
    1. use the AWS DynamoDB Local replacement. Docker image is here. Can be used on its own, and there’s a TestContainers module for it too here.
    2. Dynalite: last update in 2020, seems to be dead. The TestContainers module seems to be gone now too
  2. S3: Minio Not supported anymore, see here.
  3. SQS: https://lib.rs/crates/tc_elasticmq. Docker image here.






Wednesday, September 27, 2023

SpringDoc OpenAPI Swagger generated Swagger API shows incorrect class with same name

Introduction

When you have multiple classes with the same name in your classpath, SpringDoc with Swagger API annotations potentially picks the wrong class with the same name when generating the Swagger UI documentation.


Suppose you have these classes:

  • org.example.BookDto
  • org.example.domain.BookDto
     

And you specified your endpoint like this, where you want to have it use org.example.BookDto:

  @Operation(summary = "Get a list of books for a given shop")
  @ApiResponses(
    value = [
      ApiResponse(
        responseCode = "200",
        description = "A list of books",
        content = [Content(mediaType = "application/json",
                    array = ArraySchema(schema = Schema(implementation = BookDto::class)))]
      )
    ]
  )
  @GetMapping("/books/{shopId}")
  fun getBooksByShopId(
    @Parameter(description = "Shop to search for")
    @PathVariable shopId: Long
  ): List<BookDto> {
    return bookService.getBooksByShopId(shopId)
      .map { BooksMapper.mapDto(it) }
  }

Then whatever it finds first on the classpath will be visible in https://localhost:8080/swagger-ui.html. Not necessarily the class you meant, it might pick org.example.domain.BookDto.  

Setup:

  • Spring Boot 3
  • Kotlin 1.8
  • Springdoc OpenAPI 2.2.0
     

Solution

Several solutions exist:

Solution 1

Specify in your application.yml:

springdoc:
 use-fqn: true

 

Disadvantage: the Swagger documentation in the swagger-ui.html endpoint has then the fully specified package classpath + classname in it. Looks ugly. 

Solution 2

Setting it in the @Bean configuration:

import io.swagger.v3.core.jackson.TypeNameResolver
  @Bean
  fun openAPI(): OpenAPI? {

    TypeNameResolver.std.setUseFqn(true)
    return OpenAPI()
      .addServersItem(Server().url("/"))
      .info(
        Info().title("Books Microservice")
          .description("The Books Microservice")
          .version("v1")
      )
      .externalDocs(
        ExternalDocumentation()
          .description("Books Microservice documentation")
          .url("https://github.com/myproject/README.md")
      )
  }

Disadvantage: also in this solution the Swagger documentation in the swagger-ui.html endpoint has then the fully specified package classpath + classname in it. Looks ugly.

Solution 3

You can create your own ModelConverters, but that is much more work. Examples here:  https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions#extending-core-resolver and https://groups.google.com/g/swagger-swaggersocket/c/kKM546QXGY0

Solution 4

Make sure for each endpoint you specify the response class with full class package path:

@Operation(summary = "Get a list of books for a given shop")
  @ApiResponses(
    value = [
      ApiResponse(
        responseCode = "200",
        description = "A list of books",
        content = [Content(mediaType = "application/json",
                    array = ArraySchema(schema = Schema(implementation =
org.example.BookDto::class)))]
      )
    ]
  )
  @GetMapping("/books/{shopId}")
  fun getBooksByShopId(
    @Parameter(description = "Shop to search for")
    @PathVariable shopId: Long
  ): List<BookDto> {
    return bookService.getBooksByShopId(shopId)
      .map { BooksMapper.mapDto(it) }
  }

 See the bold Schema implementation value for what changed.


 

 

Wednesday, November 16, 2022

Spring JDBC and MySql using UUIDs in Java and VARCHAR(36) in database incorrect string value solution

Introduction

Using H2 as initial embedded database for a Spring Boot application worked fine. H2 is very forgiving in many situations and of course only tries to emulate the real target database, in this case MySql 8.0.
So after connecting my Spring Boot application to MySql, this error started to appear when inserting a row in a table with a java.util.UUID property as 'id' field:  

    java.sql.SQLException: Incorrect string value: '\xAC\xED\x00\x05sr...' for column 'id' at row 1

A quick internet search showed that potentially my character set and collate setting for the tables were using 3 bytes instead of 4 for UTF-8 storage.
But the database, tables and columns all had utf8mb4 specified as CHARACTER SET and COLLATE, since I'm using MySql 8.0. So that 3 vs 4 bytes for UTF-8 issue did not apply for me. 

Solution

Then I found this blog https://petrepopescu.tech/2021/01/how-to-use-string-uuid-in-hibernate-with-mysql/ that at least when using Hibernate, it doesn't know how to convert UUIDs to strings (varchars), and you need to specify a Hibernate-provided converter. But the Hibernate annotation of course did not work for Spring Data JDBC.

Luckily there is a way to write your own converters for Spring JDBC datatypes.

Implementing that fixed that initial error message. Note the example in the above post is missing the @Configuration annotation on the MyJdbcConfiguration class.

But then the error happened again during this custom JdbcTemplate select query:

    String query = "SELECT DISTINCT r.id, user_id FROM recipe r WHERE user_id = ?";
    List<Object> parameterValues = new ArrayList<>();
    parameterValues.add(userId);
    Object[] parameterValuesArray = parameterValues.toArray();
    jdbcTemplate.query(query, parameterValuesArray, new JdbcRecipeRowMapper());


This was the output of that query, including the parameters used in the query:

Executing prepared SQL statement [SELECT DISTINCT r.id, user_id, r.name, vegetarian, number_of_servings, instructions, r.created_at, r.updated_at FROM recipe r INNER JOIN ingredient i ON r.id = i.recipe_id WHERE user_id = ? AND vegetarian = ? AND number_of_servings = ? AND instructions LIKE ? AND  i.name IN (?) ]
2022-09-14 15:46:38.062 TRACE 16148 --- [nio-7000-exec-2] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 1, parameter value [e26b2a0a-3d2c-442f-8fa1-f26336d5a9d3], value class [java.util.UUID], SQL type unknown
2022-09-14 15:46:38.068 TRACE 16148 --- [nio-7000-exec-2] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 2, parameter value [true], value class [java.lang.Boolean], SQL type unknown
2022-09-14 15:46:38.068 TRACE 16148 --- [nio-7000-exec-2] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 3, parameter value [3], value class [java.lang.String], SQL type unknown
2022-09-14 15:46:38.068 TRACE 16148 --- [nio-7000-exec-2] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 4, parameter value [%Step%], value class [java.lang.String], SQL type unknown
2022-09-14 15:46:38.068 TRACE 16148 --- [nio-7000-exec-2] o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 5, parameter value [spinach], value class [java.lang.String], SQL type unknown
2022-09-14 16:06:37.952 DEBUG 19748 --- [nio-7000-exec-2] o.s.jdbc.core.JdbcTemplate               : SQLWarning ignored: SQL state 'HY000', error code '1366', message [Incorrect string value: '\xAC\xED\x00\x05sr...' for column 'user_id' at row 1]

So that only shows a warning message at DEBUG level, not even as an error, so at first I missed it completely! The query just returned 0 results.

    SQLWarning ignored: SQL state 'HY000', error code '1366', message [Incorrect string value: '\xAC\xED\x00\x05sr...' for column 'user_id' at row 1]

Makes sense though that this custom query has the same issue, since my string-based query of course does not use the converters that I configured earlier.
So I had to change the third line to explicitly convert the value to a string, so Spring JDBC will pass it on as a string:

        parameterValues.add(userId.toString());

Note: maybe implementing a placeholder interface for each repository like this would also have fixed it for the Spring generated methods/queries like save(), remove() etc, e.g: interface RecipeRepository extends Repository<Recipe, UUID>. Did not try that out.

Other related links used to get to the above solution:


Thursday, October 13, 2022

Migrating Java 17 Spring Boot 2.7.3 application to Kotlin 1.7.20

Introduction

This blogpost describes the challenges encountered when migrating a Java 17 Spring Boot 2.7.3 application to Kotlin 1.7.20. 

Other libraries/tools used in the project:
- Swagger (OpenAPI 3.0.3)
- Spring boot 2.7.3
- Liquibase
- H2 in mem + file based
- JUnit5 with Mockito and Mockito-Kotlin
- MySql 8.0
- Actuator
- Maven 3
Tip: after migration of most of the .java files manually, I found this Spring tutorial which helps to avoid having to add 'open' to @Component, @Service etc annotated classes.
It includes the kotlin-spring-plugin and Kotlin JPA plugin to support Kotlin features better, including support for JSR-305 annotations + Spring nullability annotations. Usually you'd also want to include jackson-module-kotlin for serialization and deserialization of Kotlin classes.
Note that my application uses generated Java classes from an OpenAPI 3 Swagger yaml file, which are returned in the REST API, so therefore not needed.

Total code reduction after migration:
Java: 4718
Kotlin: 2860

So about 44% reduction in code. Not bad.

Steps

Convert POJOs

As first I converted a simple POJO which in my case had @Data and @Builder Lombok annotations.
Open that POJO Java file. Hit ctrl-alt-shift-k. Or, find it in the IntelliJ 'actions' search panel:




Then make sure to rebuild to project by enforcing Maven to rebuild if it didn't do that.
Then I had to redo it on the POJO class.

I was a bit surprised what came out:

    @Builder
    @Data
    class Ingredient {
        private val name: String = null
        private val recipeId: UUID = null
        private val createdAt: LocalDateTime = null
        private val updatedAt: LocalDateTime = null
    }

I would have expected the Lombok annotations to be gone. But on the other hand IntelliJ can't know how to fix them I guess (see below for more on Lombok migration).
And maybe because of the @Data it made all fields private...

I did do expect more like this:

    @Builder
    @Data
    class Ingredient(val name: String, recipeId: UUID, createdAt: LocalDateTime, 
                        updatedAt: LocalDateTime)

But even when I remove the Lombok annotations, still the fields are created as private fields, not as part of the primary constructor... Maybe because of the other annotations on some of the fields, like @Id and @Version?

I manually converted it some more, into this:

    data class Ingredient(val name: String, val recipeId: UUID, val createdAt: LocalDateTime, val         
                        updatedAt: LocalDateTime)

Then I converted all uses of the Builder in the Java class to the regular (primary) constructor of the Kotlin data class. E.g:

    new Ingredient(ingredient, recipeId, createdAt, createdAt)

Doing this for all POJOs would be quite some work. And later on, you want to convert those Java instance creations to Kotlin constructors anyway, with named parameters.
So I didn't do this for all classes, I started to skip this step of replacing builders with constructors.

Now first let's try to rebuild the project with 'mvn clean install' for example.

That gave an error: the newly created Kotlin Ingredient class (symbol) could not be found.   Note that IntelliJ was able to find all dependencies just fine.
The answer to that can be found here
I applied the solution where you move your .kt file into its new src/main/kotlin/x/y/z package. Make sure to mark that src/main/kotlin directory as a source directory in IntelliJ.

But still an error: 

    Cannot find symbol (Ingredient)

So that didn't fix it, so applied the accepted solution, so to make sure the compilation order is Kotlin then Java.
After this change in the pom.xml, IntelliJ couldn't find the Spring Boot application class anymore. 'mvn clean install' ran up to the tests, but many failed due to:

    java.lang.NoClassDefFoundError: kotlin/reflect/full/KClasses 

See next section below for how those were fixed.
I changed also in the Kotlin plugin in the pom.xml the JVM target to: <jvmTarget>1.17</jvmTarget>
After that, IntelliJ compiled fine again.

Then trying to run the application with 'mvn spring-boot:run' gave this error:

    Compilation failure
    Unknown JVM target version: 1.17
    Supported versions: 1.6, 1.8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18

So the <jvmTarget> needs to be 17 (not 1.17) apparently. And then it almost started, but I got the same error as when running the tests:

    java.lang.ClassNotFoundException: kotlin.reflect.full.KClasses

Note also this warning showed up during the build, that needs to be checked and fixed, because it refers to Kotlin JDK8 and we use Java 17:

    [INFO] Scanning for projects...
    [WARNING] 
    [WARNING] Some problems were encountered while building the effective model for com.project:kotlinrecipes:jar:1.0.0
    [WARNING] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar -> duplicate declaration of version ${kotlin.version} @ line 159, column 15
    [WARNING] 
    [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.


Fixing the Java tests Part 1

Fixing the tests with the error java.lang.ClassNotFoundException: kotlin.reflect.full.KClasses

Adding this dependency, as also mentioned here, fixed it:

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>

Outstanding question for this stdlib dependency for me was, why is Kotlin stdlib using Java 8?

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>   // Can't this be Java 17?
<version>${kotlin.version}</version>
</dependency>

And: the Kotlin standard library kotlin-stdlib targets Java 6 and above. There are extended versions of the standard library that add support for some of the features of JDK 7 and JDK 8.
And when you include kotlin-stdlib-jdk8, it will pull kotlin-stdlib-jdk7 and kotlin-stdlib
And also note: https://stackoverflow.com/questions/65731542/why-is-there-no-kotlin-stdlib-jdk11. So basically, all fine since Kotlin just doesn't use any API from Java's JDK higher than 8.

After this, the application started fine, connected to the MySql Docker instance and the REST endpoints worked all fine.

Migrating the Spring Boot application class

The IntelliJ converter worked fine. The @Bean annotation in that class was migrated correctly too. Constants were correctly put in a companion object {} block.
But when starting the application this message showed up:

    org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Configuration class 'RecipesApplication' may not be final. Remove the final modifier to continue.

Strange: it says the class might not be final, remove the final modifier... Sounds contradicting!
I made the class 'open' (because the default is public final) and then it worked.

Then for the @Bean I got:

    org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'encoder' must not be private or final; change the method's modifiers to continue

I made the method 'encoder' also 'open' and then it worked.

If you have other issues, check this post for more tips. 

Migrating a Spring Boot @RestController

I applied the IntelliJ converter. Its result was pretty good. Inheritance from the OpenAPI 3 generated Java code was correctly applied. 
I had to add the Kotlin logging to replace the @Slf4j static logger, see here.
Note the default static 'log' field generated by @Slf4j is after applying that named private val logger = KotlinLogging.logger {}. But you can name it as you want of course.

When running the application, again the controller also had to be made open, to allow it to be subclassed (as can be seen in the tip in the introduction section, Spring and some other frameworks require classes to be open (extendable)).
After this change, the controller worked fine.

I also modified the generated code a bit by adding a @NotNull annotation (for something which had already a '?' so was incorrect anyway I realized afterwards), but then at runtime only I sadly got this error:

    javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method UserController#loginUser(JwtRequest) redefines the configuration of UsersApi#loginUser(JwtRequest).

Removing the incorrectly added @NotNull fixed that problem; and it is unnecessary in combination with the '?' too anyway.

Migrating @Component service

Got this error after adding the logger:

    java.lang.NullPointerException: Cannot invoke "mu.KLogger.info(String)" because "this.logger" is null

Strange, the @RestController did not have that issue. Though that one is overriding a (generated OpenAPI 3) class.
This question triggered me, so I added the 'open' keyword to the methods in the @Component class, to make it non-final so Spring has access to it with its proxies too.

That worked.

Converting Spring @Repository

For interfaces, the question was: a findByUsername(username) can return null when it doesn't find the user. How to best define that in Kotlin? Allow null to be returned (but at least the caller then has to handle the null possibility)? Or use Optional in that case? Or is there another better solution?
No clear best answer to me, e.g: https://discuss.kotlinlang.org/t/how-to-deal-with-database-null-return-according-kotlin-null-safety-feature/2546
I went for having the repo return '?'. But another repo already was using Optional, so left that there too. Will have to decide on consistency here...
See this post on how null can be seen positively and also the String.toIntOrNull() function extension built into Kotlin! :) Based on that, I went for allowing repository functions to return 0.
 

Converting @MappedCollection(idColumn = "RECIPE_ID")

Only had to make sure using the arrayOf() and using a MutableSet because you usually want to add elements to the child (collection):

        @OneToMany(cascade = arrayOf(CascadeType.ALL), orphanRemoval = true, 
                    targetEntity = Ingredient::class)
        @JoinColumn(name = "recipe_id")
        var ingredients: MutableSet<Ingredient> = HashSet<Ingredient>(),

Use @NotNull or not

Is it useful to have the @NotNull annotation, while it is only done at runtime? And doesn't Kotlin also throw an exception when you pass in a null value to a parameter that is already non-nullable by default (i.e it doesn't have the '?' appended to its type)?
Seems like double because Kotlin inserts the @NotNull into the code
Maybe you'd put it in when Java code is calling your Kotlin code, to make it more explicit - and the Java code can then validate on it?

Converting @Configuration class

A class annotated with that has also to be open: 

    @Configuration
    internal open class JdbcConfig : AbstractJdbcConfiguration() {...}

I noticed the Kotlin converter from IntelliJ didn't like always comments at the end of a line of Java code. Sometimes the '()' of a method call were then put on the wrong line.

Fixing the Java tests Part 2

While still as Java code, some failed with this:

    org.mockito.exceptions.base.MockitoException: 
    Cannot mock/spy class com.project.kotlinrecipes.user.infra.JwtUserDetailsServiceImpl
    Mockito cannot mock/spy because :
     - final class

So that was easy, I made those classes 'open'.

I also had to make methods 'open' used in Mockito.when() matchers, because otherwise it complains: 

    org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
    Invalid use of argument matchers!
    0 matchers expected, 1 recorded:

But also verify() started to fail:

    verify(jwtTokenUtil, Mockito.times(0)).validateToken(isA(String.class), isA(UserDetails.class));
    java.lang.NullPointerException: Parameter specified as non-null is null: method
    com.project.kotlinrecipes.infra.security.JwtTokenUtil.validateToken, parameter userDetails

That also meant added the 'open' keyword to that method.

Converting JUnit 5 tests with Mockito to Kotlin

IntelliJ's auto-converter works pretty good. Except that it converts

    private JdbcIngredientRowMapper jdbcIngredientRowMapper;

    @BeforeEach
    public void setUp() {
        jdbcIngredientRowMapper = new JdbcIngredientRowMapper();
    }

to:

    private var jdbcIngredientRowMapper: JdbcIngredientRowMapper? = null

    @BeforeEach
    fun setUp() {
        jdbcIngredientRowMapper = JdbcIngredientRowMapper()
    }

But it can be made nullsafe by changing it to:

    private lateinit var jdbcIngredientRowMapper: JdbcIngredientRowMapper
    
    @BeforeEach
    fun setUp() {
        jdbcIngredientRowMapper = JdbcIngredientRowMapper()
    }

I also introduced the `test description` notation, e.g:

    @Test
    fun `Should map row`() {}

I use in some tests:

    @ParameterizedTest
    @MethodSource("filterNullPermutations")

That filterNullPermutations has to be a static method. IntelliJ made it a companion object with that method 'private'. I added @JvmStatic to make it accessible for the @MethodSource.

I had to add mockito-kotlin library for better interoperability for this case:  
ArgumentMatchers.isA(class) for Kotlin methods that don't allow null values be put in (which isA() and any() for example can return).
And by adding this library I could now also use 'whenever' instead of '`when`'.

isA(MyClass.class) in Java had to be converted to:  

    whenever(jwtTokenUtil.generateToken(isA<UserDetails>))).thenReturn(BEARER_TOKEN_VALUE)

Or even shorter:

       whenever(jwtTokenUtil.generateToken(isA())).thenReturn(BEARER_TOKEN_VALUE)

using the mockito-kotlin library, which creates an instance (instead of the regular mockito which can return null). See here for more explanation. 

Replaced all mock() with Kotlin style: val mockBookService : BookService = mock()

The generated code did not work for this TestRestTemplate.exchange() call in Java:

        // Set up find parameters
        Map<String, String> uriVariables = new HashMap<>();
        uriVariables.put("vegetarian", "true");
        uriVariables.put("numberOfServings", "3");
        uriVariables.put("includedIngredients", "onion");
        uriVariables.put("excludedIngredients", "fish");
        uriVariables.put("instructions", "First");

        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
        HttpEntity<?> entity = new HttpEntity<>(headers);

        // When
        ResponseEntity<List<RecipeResponse>> foundRecipesEntity = testRestTemplate.exchange("/recipes/findByFilter?vegetarian={vegetarian}&numberOfServings={numberOfServings}&includedIngredients={includedIngredients}&excludedIngredients={excludedIngredients}&instructions={instructions}",
                HttpMethod.GET, entity, new ParameterizedTypeReference<>() {}, uriVariables);


Became after IntelliJs converter applied:

        // Set up find parameters
        val uriVariables: MutableMap<String, String?> = HashMap()
        uriVariables["vegetarian"] = "true"
        uriVariables["numberOfServings"] = "3"
        uriVariables["includedIngredients"] = "onion"
        uriVariables["excludedIngredients"] = "fish"
        uriVariables["instructions"] = "First"
        val headers = HttpHeaders()
        headers[HttpHeaders.ACCEPT] = MediaType.APPLICATION_JSON_VALUE
        val entity: HttpEntity<*> = HttpEntity<Any>(headers)

        // When
        val foundRecipesEntity: ResponseEntity<List<RecipeResponse>> =
            testRestTemplate.exchange<List<RecipeResponse>>("/recipes/findByFilter?vegetarian={vegetarian}&numberOfServings={numberOfServings}&includedIngredients={includedIngredients}&excludedIngredients={excludedIngredients}&instructions={instructions}",
                HttpMethod.GET, entity, object : ParameterizedTypeReference<List<RecipeResponse?>?>() {}, uriVariables
            )

But .exchange() was red underlined, no matching method to invoke found. I had to change it into this:

        // Set up find parameters
        val uriVariables: MutableMap<String, String> = HashMap()
        uriVariables["vegetarian"] = "true"
        uriVariables["numberOfServings"] = "3"
        uriVariables["includedIngredients"] = "onion"
        uriVariables["excludedIngredients"] = "fish"
        uriVariables["instructions"] = "First"
        val headers = HttpHeaders()
        headers[HttpHeaders.ACCEPT] = MediaType.APPLICATION_JSON_VALUE

        // When
        val foundRecipesEntity: ResponseEntity<List<RecipeResponse>>? =
            testRestTemplate.exchange(
                "/recipes/findByFilter?vegetarian={vegetarian}&numberOfServings={numberOfServings}&includedIngredients={includedIngredients}&excludedIngredients={excludedIngredients}&instructions={instructions}",
                HttpMethod.GET, HttpEntity("parameters", headers),
                typeReference<List<RecipeResponse>>(), uriVariables
            )

With the typeReference method added (you can also do it inline BTW):

    private inline fun <reified T> typeReference() = object : ParameterizedTypeReference<T>() {}

And after some more cleaning up this worked too (can you spot the differences with the generated Kotlin from IntelliJ?):

        // Set up find parameters
        val uriVariables: MutableMap<String, String> = HashMap()
        uriVariables["vegetarian"] = "true"
        uriVariables["numberOfServings"] = "3"
        uriVariables["includedIngredients"] = "onion"
        uriVariables["excludedIngredients"] = "fish"
        uriVariables["instructions"] = "First"
        val headers = HttpHeaders()
        headers[HttpHeaders.ACCEPT] = MediaType.APPLICATION_JSON_VALUE
        val entity: HttpEntity<*> = HttpEntity<Any>(headers)

        // When
        val foundRecipesEntity: ResponseEntity<List<RecipeResponse>> =
            testRestTemplate.exchange(
                "/recipes/findByFilter?vegetarian={vegetarian}&numberOfServings={numberOfServings}&includedIngredients={includedIngredients}&excludedIngredients={excludedIngredients}&instructions={instructions}",
                HttpMethod.GET, entity,
                typeReference<List<RecipeResponse>>(), uriVariables
            )


Note that MockK can be the next improvements to the Kotlin code, to allow more Kotlin-style of notation.

Method documentation generation

I noticed my IntelliJ 2022.2.1 does not generate @param, @return etc documentation when typing /** above a (private) function definition.

Miscellaneous

I still have a few references to Java classes in the code, like this one:

        httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter::class.java)

Could not find a way to avoid having to reference a Java class in Kotlin this directly.

And at the end of the process I removed all Lombok annotations and its dependency in the pom.xml.

Bonus tip

Spring's Kotlin extensions overview can be found here.