When deserializing TlsContextConfiguration from YAML using Jackson, field default values are not respected and the deserialized object contains the default value for that type of field (null) for reference types, and the default value for primitive types (e.g., false for boolean).
The tests have always used SnakeYAML to deserialize, and they've always passed. And in all of our Dropwizard services, we use dropwizard-config-providers to provide default values for key/trust stores and thus we don't have explicit configuration for TlsContextConfiguration in our YAML.
But recently for some new development work, I needed to have an explicit configuration for TlsContextConfiguration in a YAML configuration for a Dropwizard application, and found that it was not respecting the default values for keyStoreType and trustStoreType (JKS), for protocol (TLSv1.2), and for verifyHostname (true). Instead, keyStoreType, trustStoreType, and protocol were all null and verifyHostname was false. And yet all the tests pass in TlsContextConfigurationTest! (I guess tests are worthless after all 🤣 )
Long story short, it was because of the Lombok annotations used in TlsContextConfiguration.
TlsContextConfiguration has the following Lombok annotations at the class-level:
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE) // for Builder (b/c also need no-args constructor)
It also uses @Builder.Default on protocol, keyStoreType, trustStoreType, and verifyHostname. Over time we've found that this combination of Lombok annotations does not work as you would expect in some situations, depending on whether you use the builder, the no-args constructor, or the all-args constructor. The builder and no-args constructor work as expected, while the all-args constructor does not.
But why did the tests pass using SnakeYAML, and why did the deserialization not work as expected in the Dropwizard application? When I de-lomboked TlsContextConfiguration, the fields each still have their default value, so when the no-args constructor is used along with setter methods, the default values are respected unless overwritten. But the all-args constructor does not respect the default values. Here is the de-lomboked all-args constructor code:
When I ran the tests through the debugger, I found that SnakeYAML uses the no-args constructor and then calls setter methods, while Jackson (which is what Dropwizard uses under the hood), uses the all-args constructor. When Jackson parses YAML that does not contain values for all properties, it passes default values to the all-args constructor, e.g., null for reference types and false for primitive boolean. This is why the fields with default values are incorrect in the deserialized object when using Jackson. I'm sure there are some ways to configure Jackson to force it to use the no-args constructor but the easier solution is to simply remove the top-level Lombok constructor annotations and the @Builder.Default ones, and write the constructors manually. This way the all-args constructor can respect the field default values.
When deserializing
TlsContextConfiguration
from YAML using Jackson, field default values are not respected and the deserialized object contains the default value for that type of field (null
) for reference types, and the default value for primitive types (e.g.,false
forboolean
).The tests have always used SnakeYAML to deserialize, and they've always passed. And in all of our Dropwizard services, we use dropwizard-config-providers to provide default values for key/trust stores and thus we don't have explicit configuration for
TlsContextConfiguration
in our YAML.But recently for some new development work, I needed to have an explicit configuration for
TlsContextConfiguration
in a YAML configuration for a Dropwizard application, and found that it was not respecting the default values forkeyStoreType
andtrustStoreType
(JKS
), forprotocol
(TLSv1.2
), and forverifyHostname
(true
). Instead,keyStoreType
,trustStoreType
, andprotocol
were allnull
andverifyHostname
wasfalse
. And yet all the tests pass inTlsContextConfigurationTest
! (I guess tests are worthless after all 🤣 )Long story short, it was because of the Lombok annotations used in
TlsContextConfiguration
.TlsContextConfiguration
has the following Lombok annotations at the class-level:It also uses
@Builder.Default
onprotocol
,keyStoreType
,trustStoreType
, andverifyHostname
. Over time we've found that this combination of Lombok annotations does not work as you would expect in some situations, depending on whether you use the builder, the no-args constructor, or the all-args constructor. The builder and no-args constructor work as expected, while the all-args constructor does not.But why did the tests pass using SnakeYAML, and why did the deserialization not work as expected in the Dropwizard application? When I de-lomboked
TlsContextConfiguration
, the fields each still have their default value, so when the no-args constructor is used along with setter methods, the default values are respected unless overwritten. But the all-args constructor does not respect the default values. Here is the de-lomboked all-args constructor code:When I ran the tests through the debugger, I found that SnakeYAML uses the no-args constructor and then calls setter methods, while Jackson (which is what Dropwizard uses under the hood), uses the all-args constructor. When Jackson parses YAML that does not contain values for all properties, it passes default values to the all-args constructor, e.g.,
null
for reference types andfalse
for primitiveboolean
. This is why the fields with default values are incorrect in the deserialized object when using Jackson. I'm sure there are some ways to configure Jackson to force it to use the no-args constructor but the easier solution is to simply remove the top-level Lombok constructor annotations and the@Builder.Default
ones, and write the constructors manually. This way the all-args constructor can respect the field default values.