Configure Jackson to tolerate Podman's response to docker context ls
Closed this issue · 4 comments
Environment
- Spring Boot: 4.0.0-RC1
- Jackson: 3.0.1
- Container runtime: Podman 5.6.2 with all Docker compatibility features enabled
- Spring Modulith: 2.0-RC1
- macOS 26.1
Description
When using the spring-boot-docker-compose dependency with Podman instead of Docker, the startup fails because of the change in Jackson 3.0 to set FAIL_ON_NULL_FOR_PRIMITIVES to true.
This would not be an issue, if it was possible to set this back to false using either application.properties or JsonMapperBuilderCustomizer, but this does not seem to be possible because of the timing during application startup and the early initialization of the Docker Compose feature. Neither setting is picked up and therefore the deserialization in DockerJson and the static configuration cannot be changed.
The root cause for the issue is the different response Podman gives to the docker context ls command. This does not conform with the Docker response as it does not include a current field, which is in turn a primitive boolean in DockerCliContextResponse. This causes the issue with the new Jackson 3.0 defaults.
Podman response to docker context ls:
{"Name":"podman-machine-default","URI":"ssh://core@127.0.0.1:55599/run/user/501/podman/podman.sock","Identity":"/Users/user/.local/share/containers/podman/machine/machine","IsMachine":true,"Default":false,"ReadWrite":true}
{"Name":"podman-machine-default-root","URI":"ssh://root@127.0.0.1:55599/run/podman/podman.sock","Identity":"/Users/user/.local/share/containers/podman/machine/machine","IsMachine":true,"Default":true,"ReadWrite":true}Full stacktrace of the issue:
tools.jackson.databind.exc.MismatchedInputException: Cannot map `null` into type `boolean` (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: org.springframework.boot.docker.compose.core.DockerCliContextResponse["current"])
at tools.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:67) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1802) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.jdk.NumberDeserializers$PrimitiveOrWrapperDeserializer.getNullValue(NumberDeserializers.java:167) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.ValueDeserializer.getAbsentValue(ValueDeserializer.java:384) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.bean.PropertyValueBuffer._findMissing(PropertyValueBuffer.java:279) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.bean.PropertyValueBuffer.getParameters(PropertyValueBuffer.java:208) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.ValueInstantiator.createFromObjectWith(ValueInstantiator.java:270) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.bean.PropertyBasedCreator.build(PropertyBasedCreator.java:252) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.bean.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:697) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.bean.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1417) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.bean.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:480) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.bean.BeanDeserializer.deserialize(BeanDeserializer.java:200) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.deser.DeserializationContextExt.readRootValue(DeserializationContextExt.java:265) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2610) ~[jackson-databind-3.0.1.jar:3.0.1]
at tools.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1564) ~[jackson-databind-3.0.1.jar:3.0.1]
at org.springframework.boot.docker.compose.core.DockerJson.deserialize(DockerJson.java:73) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerJson.deserialize(DockerJson.java:69) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerJson.lambda$deserializeToList$0(DockerJson.java:58) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
at java.base/java.lang.StringLatin1$LinesSpliterator.forEachRemaining(StringLatin1.java:688) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[na:na]
at org.springframework.boot.docker.compose.core.DockerJson.deserializeToList(DockerJson.java:58) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerCliCommand.deserialize(DockerCliCommand.java:85) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerCli.run(DockerCli.java:83) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DefaultDockerCompose.lambda$new$0(DefaultDockerCompose.java:48) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerHost.fromCurrentContext(DockerHost.java:104) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerHost.get(DockerHost.java:90) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerHost.get(DockerHost.java:75) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DefaultDockerCompose.<init>(DefaultDockerCompose.java:48) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.core.DockerCompose.get(DockerCompose.java:148) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.lifecycle.DockerComposeLifecycleManager.getDockerCompose(DockerComposeLifecycleManager.java:166) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.lifecycle.DockerComposeLifecycleManager.start(DockerComposeLifecycleManager.java:114) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.lifecycle.DockerComposeListener.onApplicationEvent(DockerComposeListener.java:53) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.docker.compose.lifecycle.DockerComposeListener.onApplicationEvent(DockerComposeListener.java:35) ~[spring-boot-docker-compose-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:180) ~[spring-context-7.0.0-RC2.jar:7.0.0-RC2]
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:173) ~[spring-context-7.0.0-RC2.jar:7.0.0-RC2]
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:151) ~[spring-context-7.0.0-RC2.jar:7.0.0-RC2]
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:133) ~[spring-context-7.0.0-RC2.jar:7.0.0-RC2]
at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:137) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.context.event.EventPublishingRunListener.contextLoaded(EventPublishingRunListener.java:99) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.SpringApplicationRunListeners.lambda$contextLoaded$0(SpringApplicationRunListeners.java:74) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:123) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:117) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.SpringApplicationRunListeners.contextLoaded(SpringApplicationRunListeners.java:74) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:418) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1374) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-4.0.0-RC1.jar:4.0.0-RC1]
at com.example.demo.DemoApplication.main(DemoApplication.java:14) ~[main/:na]
I found no way to make the Docker Compose feature work with Podman in Spring Boot 4.0.0-RC1 because of this issue. I know there is some discussion about adding full Podman support in the future, but until then it would be great if the Docker Compose feature would work as it did in Spring Boot 3.5.
Given containers/podman#18521, I'm somewhat surprised that this worked in 3.x. The only thing that's available from docker context ls with Podman is the name of the context. current will always be false and dockerEndpoint will always be null.
Given that we don't yet officially support Podman, I don't consider this to be a regression. We can make Jackson more tolerant of Podman's responses again though.
After your comment, I check again with Spring Boot 3.5.7 to be sure.
The issue must be the same internally, but since the null value for current is just set to false by Jackson 2, it seems to work. The log looks like this:
2025-11-06T10:21:38.004+01:00 INFO 17261 --- [demo] [ main] .s.b.d.c.l.DockerComposeLifecycleManager : Using Docker Compose file /Users/user/Downloads/demo 8/compose.yaml
2025-11-06T10:21:38.682+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Network demo8_default Creating
2025-11-06T10:21:38.683+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Network demo8_default Created
2025-11-06T10:21:38.684+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container demo8-postgres-1 Creating
2025-11-06T10:21:38.728+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container demo8-postgres-1 Created
2025-11-06T10:21:38.730+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container demo8-postgres-1 Starting
2025-11-06T10:21:38.821+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container demo8-postgres-1 Started
2025-11-06T10:21:38.821+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container demo8-postgres-1 Waiting
2025-11-06T10:21:39.325+01:00 INFO 17261 --- [demo] [utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container demo8-postgres-1 Healthy
And the database container is up and running in Podman and the Spring application has started successfully.
Thanks for that, @sebelsc, and for trying the RC.
Looking at the code, as long as we tolerate Podman's response, none of its contexts will be identified as the current context and we'll fall back to using localhost:
With 4.0.0-RC1, I think you should be able to work around the problem by setting a DOCKER_HOST environment variable as that'll short circuit things before we try to determine the current context.
Thank you for the quick response and fix.
I will try and work with the DOCKER_HOST environment variable.