Gradle build of new branch fails with UnexpectedCharacterException SemVer in nebula.release plugin
The best articles and links to interesting posts for technical team leaders building sophisticated websites, applications and mobile apps. Think about: software architecture, hardware architecture, design, programming, frameworks, scalability, performance, quality assurance, security, resolving issues, fixing bugs and Android.
Geplaatst door
Techie
op
2:42 PM
0
reacties
Labels: branch, branch name, exception, gradle, kotlin, nebula, nebula.release, new branch, plugin, semver, Spring Boot 4, UnexpectedCharacterException
Add to:
Del.icio.us
DZone
Reddit
Digg
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.
Several options exist for full AWS stack replacements.
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.
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!
For building a Docker image for the JVM version, you can run these commands in the root directory of the Floci project:
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.
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()
}
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.
Multiple options exist, this list might not be listing all options.
Geplaatst door
Techie
op
2:13 PM
0
reacties
Labels: aws, docker, DynamoDB, floci, integration testing, localstack, sns, spring boot, SQS, testcontainers
Add to:
Del.icio.us
DZone
Reddit
Digg
Geplaatst door
Techie
op
1:20 PM
0
reacties
Labels: camera android, DCIM, directory, mobile phone, not found, photo, photos, pixel 6, pixel 6x, usb
Add to:
Del.icio.us
DZone
Reddit
Digg
Geplaatst door
Techie
op
10:00 AM
0
reacties
Labels: forwarding, jakarta servlet, java, kotlin, modelandview, oas, server-side, serverside, spring boot 3, spring mvc, swagger
Add to:
Del.icio.us
DZone
Reddit
Digg
To mock a Retrofit2 call which returns Call<Void?> in the interface client like this:
@POST("/events")
fun registerEvent(@Body requestDto: EventRequestDto): Call<Void?>
is not super-obvious, since it can show this error when invoked:
Cannot invoke "retrofit2.Call.request()" because "call$iv" is null
java.lang.NullPointerException: Cannot invoke "retrofit2.Call.request()" because "call$iv" is null
How do you mock a static companion method in Kotlin using MockK?
I wanted to mock this (static) method in a companion object in Kotlin in class MyClass:
companion object {
fun isNegativeAndFromSavingsAccount(amount: BigDecimal, accountType: accountType) = amount < BigDecimal.ZERO && accountType == AccountType.SAVINGS
}
Trying it with a regular 'every' like this doesn't work:
every { MyClass.isNegativeAndFromSavingsAccount(any(), any()) } returns false
Note it does compile fine!
But when running the test, you'll get this error:
io.mockk.MockKException: Failed matching mocking signature for left matchers: [any(), any()]
at io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:97)
Setup:
This is the way it does work:
import io.mockk.mockkObject
mockkObject(MyClass.Companion) {
every { MyClass.isNegativeAndFromSavingsAccount(any(), any()) } returns false
}
Note this did not work, got the same error:
mockkObject(MyClass::class)
every { MyClass.isNegativeAndFromSavingsAccount(any(), any()) } returns false
I found several posts, but none of them gave a clear answer and/or were using some older version of MockK. E.g: https://github.com/mockk/mockk/issues/61
and this StackOverflow post.
Some more examples and variants of solutions can be found here, e.g when using @JvmStatic.
Geplaatst door
Techie
op
10:48 AM
2
reacties
Labels: kotlin, mock, mocking, mockk, mockkObject, staticMockk, test, testing, unittest, unittesting
Add to:
Del.icio.us
DZone
Reddit
Digg
Recently, the NVD (National Vulnerability Database) which the Owasp dependency check plugin uses to get its data from to check for vulnerabilities, has introduced the use of an API key. That's for them to better control access and throttling - imagine how many companies and organizations are using that API, each time a dependency check build is performed. Especially those that don't cache the NVD database and at each run retrieve it again. And be aware: "... previous versions of dependency-check utilize the NVD data feeds which will be deprecated on Dec 15th, 2023. Versions earlier then 9.0.0 are no longer supported and could fail to work after Dec 15th, 2023."
But this introduction doesn't go without some hiccups. For example it is possible to still get HTTP 403 Forbidden responses, even while you have a valid key. Here's my research while trying to fix it.
Setup:
First you should check if your API key is valid by execution this command:
curl -H "Accept: application/json" -H "apiKey: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -v https://services.nvd.nist.gov/rest/json/cves/2.0\?cpeName\=cpe:2.3:o:microsoft:windows_10:1607:\*:\*:\*:\*:\*:\*:\*
(where xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx is your NVD API key)
That should return JSON (and not a 404). Now you know your API key is valid.
Some have some success with setting the delay longer:
nvd {
apiKey = System.getProperty("ENV_NVD_API_KEY")
delay = 6000 // milliseconds, default is 2000 with API key, 8000 without
}
Commandline version:
--nvdDelay 6000
You can also increase the validForHours option, but that doesn't work if during you construct completely new Docker containers each build - you lose that history.
All NVD options you can pass to DependencyCheck are found here.
But currently (27 December 2023) all the above efforts don't always fix the problem of the 403. Sometimes it works for a while, but then not again. If you build many projects in your company at about the same time, you still have a chance of getting throttled of course.
The best solution is to create a local cache so you are less dependent on NVID API calls (and thus the throttling).
Geplaatst door
Techie
op
1:26 PM
0
reacties
Labels: 403, api, dependencycheck, forbidden, gradle, HTTP 403, local cache, NVD, NVD API, nvd.delay, nvd.key
Add to:
Del.icio.us
DZone
Reddit
Digg
Setup:
- Mapstruct 1.5.5
- Kotlin 1.8
- IntelliJ 2023.2.3
For a mapping between two classes, the source class did not have the mandatory (non-null, Kotlin!) target class field created. And I wanted to fill it with the current ZonedDateTime class, with the timezone of Amsterdam/Europe. And that specific ZoneId is a constant in my Application.kt class named MY_DEFAULT_TIME_ZONE.
So I looked at the expression field in @Mapping found here.
I got this solution working quite fast with:
@Mapping(target = "created", expression = "java(ZonedDateTime.now())").
But as you see, the ZoneId constant is still missing.
I had to try quite a few things to get that working, because the class ZoneId was not getting recognized in the generated implementation MapStruct mapper.
In the end this worked:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, imports = [ZoneId::class, Application::class])
interface MyMapper {
@Mapping(target = "created", expression = "java(ZonedDateTime.now(my.package.Application.MY_DEFAULT_TIME_ZONE))")
fun myDtoToResponseDto(myDto: MyDto): ResponseDto
...
}
Note the imports field to have the class ZoneId and the constant available (imported) in the implementation class, generated by MapStruct.
In the Application.kt you then have to make the MY_DEFAULT_TIME_ZONE constant available to Java, since that's what MapStruct uses as language:
Application.kt
{
companion object {
@JvmField
val MY_DEFAULT_TIME_ZONE: ZoneId = ZoneId.of("Europe/Amsterdam")
...
}
I also tried this:
@Mapping(target = "created", source = ".", qualifiedByName = ["getValue"])
with a function:
@Named(value = "getValue")
fun getValue(myDto: MyDto): ZonedDateTime {
return ZonedDateTime.now(MY_DEFAULT_TIME_ZONE)
}
The advantage of this solution is that you can use Kotlin code and you don't have to wait and see if your expression has the correct syntax and will compile.
But then I got this error: ZonedDateTime does not have an accessible constructor. I also tried to wrap the field created in a small class, but that didn't work either (could be me :)
See this and this for more details on how that should work.
I also tried with the @JvmDefault annotation, but that is deprecated + it requires you to use the -Xjvm-default property, which I couldn't get to work in IntelliJ with Gradle.
And it is not always guaranteed to work, see here and here and here:
I'm definitely still a beginner in using MapStruct. So probably one of the other methods could work too... Any tips are welcome :)
Geplaatst door
Techie
op
2:18 PM
0
reacties
Labels: @mapping, expression, expressions, intellij, kotlin, mapstruct, ZonedDateTime
Add to:
Del.icio.us
DZone
Reddit
Digg
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.
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:
Several solutions exist:
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.
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.
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
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.
Geplaatst door
Techie
op
12:46 PM
0
reacties
Labels: class, fqn, fully qualified name, incorrect class, kotlin, openapi, openapi 3, spring boot, springdoc, swagger, swagger-ui.html, wrong class
Add to:
Del.icio.us
DZone
Reddit
Digg