Spring DateTimeFormat Configuration for java.time - java

I'm working on a Spring WebMvc (not Spring Boot) project that uses pure Java configuration for setting up its beans. I am having difficulty getting Spring/Jackson to respect the #DateTimeFormat annotation with java.time (jsr310) objects such as LocalDateTime.
I have both jackson-datatype-jsr310 and jackson-databind jars (version 2.7.4) on the classpath, along with the relevant spring jars for a basic webmvc application spring-context and spring-webmvc (version 4.3.0.RELEASE)
Here is my relevant configuration class:
#Configuration
#ComponentScan({"com.example.myapp"})
public class WebAppConfig extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper mapper = Jackson2ObjectMapperBuilder
.json()
.indentOutput(true)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.findModulesViaServiceLoader(true)
.build();
converters.add(new MappingJackson2HttpMessageConverter(mapper));
super.addDefaultHttpMessageConverters(converters);
}
}
I've tested this with serializing my data models over a rest controller. Appears that Jackson is respecting #JsonFormat, but completely ignoring #DateTimeFormat.
What additional configuration am I missing to get spring/jackson to respect #DateTimeFormat? Are there any key differences between the two annotations that I should be aware of, problems that I could run into just by using #JsonFormat?

#JsonFormat is a Jackson annotation; #DateTimeFormat is a Spring annotation.
#JsonFormat will control formatting during serialization of LocalDateTime to JSON.
Jackson doesn't know about Spring's #DateTimeFormat, which is used to control formatting of a bean in Spring when it's rendered in the JSP view.
Javadocs:
http://docs.spring.io/spring-framework/docs/4.2.3.RELEASE/javadoc-api/org/springframework/format/annotation/DateTimeFormat.html
http://static.javadoc.io/com.fasterxml.jackson.core/jackson-annotations/2.7.5/com/fasterxml/jackson/annotation/JsonFormat.html

Related

How do I set Jackson's constructorDetector with Spring Boot?

I want to configure Jackson so that it automatically deserializes using constructors, without needing annotations. With Spring Boot, this works out of the box for most constructors but not single argument constructors.
Jackson 2.12 has released a configuration option to enable deserialization for single argument constructors as well:
ObjectMapper mapper = JsonMapper.builder()
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
.build()
However, this doesn't use the usual Feature enabling/disabling interface. How can I set this with Spring Boot?
I don't want to lose any Spring Boot autoconfiguration that is being applied to the ObjectMapper bean.
I can't define a Jackson2ObjectMapperBuilder bean because, as of Spring Boot 2.4.1, this has not yet been updated to allow constructorDetector to be set.
Defining a bean of either Jackson2ObjectMapperBuilder or ObjectMapper will prevent application of any auto-configuration for these beans, as documented.
Instead, you can define a bean of type Jackson2ObjectMapperBuilderCustomizer which is a lambda that lets you call additional methods on the Spring Boot auto-configured Jackson2ObjectMapperBuilder.
Additionally, Jackson2ObjectMapperBuilder has the method postConfigurer which is another call back which lets you call methods on the auto-configured ObjectMapper.
Putting these together:
#Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> builder.postConfigurer(mapper ->
mapper.setConstructorDetector(USE_PROPERTIES_BASED)
);
}

#EnableWebMvc showing date in array formate

We had two application on spring boot. One was spring rest api based & second was spring MVC based.
We have megred both the application due to some business reasons as the context was the same and everything is working fine except java.time.LocalDateTime formatting that does by spring automatically on rest API.
previously it was formatting LocalDateTime as "2018-08-30T18:13:24"
but after merging it is showing as [
2018,
08,
30,
18,
13,
24
],
I have found out #EnableWebMVC annotation is the culprit but after removing that annotation web-mvc pages do not work.
What should I do so that date display in ISO (String) format and view resolver & jsp pages works fine?
Please help thanks.
Everyone is saying #EnableWebMvc is the culprit.
But, no one is saying with WebMvc how to resolve this issue.
So, to answer the question, yes, there is a way to resolve this issue by not removing the #EnableWebMvc.
Before moving into the answer, let's understand a few concepts:
HttpMessageConverters -> These are the ones that convert Java Objects from and to JSON/XML
By default, Spring boot will add the following converters:
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
SourceHttpMessageConverter
FormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJackson2XmlHttpMessageConverter
MappingJackson2HttpMessageConverter
So, whenever we are converting the java object into JSON, then spring will go through this list of converters one by one in order and picks the relevant one to convert
Now, if we add our custom MappingJackson2HttpMessageConverter to this list as the last element, then spring will not come to it because before reaching our converter(9th element), there is the default converter at the 7th index
So, to resolve this issue, we need to remove the default MappingJackson2HttpMessageConverte and need to add our custom converter
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// Remove the default MappingJackson2HttpMessageConverter
converters.removeIf(converter -> {
String converterName = converter.getClass().getSimpleName();
return converterName.equals("MappingJackson2HttpMessageConverter");
});
// Add your custom MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(objectMapper);
converters.add(converter);
WebMvcConfigurer.super.extendMessageConverters(converters);
}
}
Note: Please don't use configureMessageConverters() instead of extendMessageConverters() from WebMvcConfigurer because configure method will remove all the existing converters which will be installed by default.
Hope it will help someone like me who has wasted some hours debugging the issue :)
If you are using Jackson as your JSON <-> POJO mapper, you can set the following properties:
spring:
jackson:
date-format: yyyy-MM-dd'T'hh:mm:ss
serialization:
write-dates-as-timestamps: false
spring.jackson.serialization.write-dates-as-timestamps defaults to true, which serializes LocalDateTime as an array, like the one you show.
For a finer-grained control, you can also annotate date-time fields like following:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'hh:mm:ss")
private LocalDateTime datetime;
It takes precedence over the above property.
Take a look at other JSON related Spring Boot properties here: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#json-properties
For those still facing the issue, #EnableWebMVC is definitely the culprit as it disables the Web MVC auto-configuration default behaviors in favor of yours.
About that, the Spring Boot reference documentation says:
Spring MVC Auto-configuration
...
If you want to take complete control of Spring MVC, you can add your own #Configuration annotated with #EnableWebMvc ...
However, you can add custom configuration without losing the default auto-configuration behaviors.
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own #Configuration class of type WebMvcConfigurer but without #EnableWebMvc.
That said, removing #EnableWebMVC should work just fine.

Spring Boot Autoconfigured ObjectMapper plus additional ObjectMapper

We are currently trying to implement a JSON Logging library making use of spring auto configuration or create its Jackson ObjectMapper. Our aim is to not override the spring auto configuration in class JacksonAutoConfiguration so that every customization by clients of the logging library won't be disabled.
The actual spring behavior is bean based and our main problem is that the JacksonProperties are not customizable and reusable for us. If we actually add a second bean of JacksonProperties the application start up would fail because JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.class won't be able to handle a second bean. (The Spring Boot internal one is not annotated as #Primary.)
So what we did was start reimplementing every bean like the builder, customizer and so on. But this is not very maintainable as it duplicates framework code.
Our question now is if there would be any way to adapt the way of creating data sources for jackson object mapper beans. An example of creating data sources would be a following one.
#Bean(name = "testDataSource")
#ConfigurationProperties(prefix = "test.datasource")
public HikariDataSource naeDataSource(DataSourceProperties testDataSourceProperties) {
return testDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
We know the problem would still be that overriding the object mapper would deactivate it but if you pay attention to the application context it would be much easier to offer multiple object mapper instances.
Or is there any easier or other way we did not find so far?
Versions used:
Spring-Boot 2.0.5.RELEASE
UPDATE
I forked the spring boot project, made some changes to Spring Boot Autoconfiguration module and also set up a small demo project. I do not think it is the perfect way but with this changes it would be possible to initialize own object mapper easily from configuration properties. For example you can now easily create five object mapper for five different rest templates and clients called via REST-API.
#ConfigurationProperties(prefix = "logging.jackson")
#Bean("loggingJacksonProperties")
public JacksonProperties loggingJacksonProperties() {
return new JacksonProperties();
}
#Bean
public ObjectMapper secondObjectMapper(#Qualifier("loggingJacksonProperties") JacksonProperties loggingJacksonProperties) {
return loggingJacksonProperties.initializeJackson2ObjectMapperBuilder().build();
}
Comparing-Fork: https://github.com/spring-projects/spring-boot/compare/2.1.x...mixaaaa:feature/jackson_properties_initializer
Demo-Project: https://github.com/mixaaaa/jackson-demo

Jackson serialization is not working after Spring Boot upgrade

Yesterday I started with upgrade from Spring Brussels-SR3 to Spring Brussels-SR6.
The Spring Boot goes from 1.5.4. to 1.5.9, Jackson goes from 2.8.8 to 2.8.10). I am using HATEOAS and HAL links. It means my Jackson configuration looks like this:
#Configuration
public class JacksonConfiguration {
private static final String SPRING_HATEOAS_OBJECT_MAPPER = "_halObjectMapper";
#Autowired
#Qualifier(SPRING_HATEOAS_OBJECT_MAPPER)
private ObjectMapper springHateoasObjectMapper;
#Primary
#Bean(name = "objectMapper")
ObjectMapper objectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(DateTimeFormatter.ISO_INSTANT));
springHateoasObjectMapper.registerModules(javaTimeModule);
springHateoasObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
springHateoasObjectMapper.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
springHateoasObjectMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
springHateoasObjectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
springHateoasObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
springHateoasObjectMapper.enable(SerializationFeature.INDENT_OUTPUT);
springHateoasObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return springHateoasObjectMapper;
}
}
It means I am reusing the _halObjectMapper bean and adding some more configurations. It had been working until I started with the upgrade.
What is wrong?
The thing that goes wrong is that after the upgrade all my serialization customizations and HAL conventions are not applied- datetime formats, indenting HAL "_links" JSON field changes to "links" ... So the _halObjectMapper is not used for serialization any more.
Any clue what could be the issue or where should I dig to figure out what is wrong?
Additional info after some debugging:
I have figured out that TypeConstrainedMappingJackson2HttpMessageConverter that uses _halObjectMapper is not used for conversion to json anymore. The reason is that it does not get to the collection of converters when starting spring. It looks like it is not created for RequestMappingHandlerAdapter bean because of some condition that skips creation when Jackson2HalModule is allready registered in some other converter (in this case ProjectingJackson2HttpMessageConverter).
Any idea what could be a cause or where to look at to figure out why the spring boot start proceeds differently?
Additional info after some more debugging:
The difference I see before and after the upgrade is that before the upgrade the ProjectingJackson2HttpMessageConverter was populated with new instance of ObjectMapper. But after the upgrade, the ObjectMapper is resolved from container so the _halObjectMapper is chosen. As a result the ProjectingJackson2HttpMessageConverter matches as a converter with registered halModule and TypeConstrainedMappingJackson2HttpMessageConverter creation is ommited for RequestMappingHandlerAdapter.
One more interesting thing is that there are two more microservices I upgraded. The difference is that one has the same issue and one is working. The one that is working has different spring security and oauth2 setup. Bean of class OAuth2RestTemplate is not defined in the microservice that is working. Microservices with OAuth2RestTemplate have the issue. The reason why I am pointing this out is that there is different in the initialization behavior in these two cases. The OAuth2RestTemplate rest template is populated with these converters too and it might affect the initialization process.
Temporary solution
As a temporary hotfix I have downgraded spring-data-commons from 1.13.6.RELEASE to 1.13.6.RELEASE. However the newer code makes more sense to me.
I am still trying to achieve some better understanding and figure out correct approach
I don't know if it is helpfull to you, but I had a very similar problem with a Spring Boot upgrade from Version 2.0.3 to 2.0.4. I still don't know what exactly caused the problem, but the solution was to create Beans for every Module I use instead of replacing the default ObjectMapper. In your case it would look something like this:
#Configuration
public class JacksonConfiguration {
#Bean
JavaTimeModule javaTimeModule () {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(DateTimeFormatter.ISO_INSTANT));
return javaTimeModule;
}
}
All Features can be set via the applications.properties file like this:
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=false
spring.jackson.deserialization.READ_DATE_TIMESTAMPS_AS_NANOSECONDS=false
and so on. For more information on how to configure the default object mapper without actually replacing it see https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-the-jackson-objectmapper and https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

Should I annotate configuration class as #Configuration for testing?

I spent some time resolving problem with missing org.joda.time.DateTime->java.util.Date converter in Spring Data (which should be enabled by default when Joda-Time is on a classpath). I have found a reason, but it generated a question about #Configuration annotation in Spring.
Standard application config using AbstractMongoConfiguration from spring-data-mongodb:
#Configuration
#ComponentScan
#EnableMongoRepositories
public class AppConfig extends AbstractMongoConfiguration { ... }
A test which explicit uses AppConfig class (with Spock, but internally mechanisms provided by spring-test are used):
#ContextConfiguration(classes = AppConfig)
class JodaDocRepositorySpec extends Specification {
#Autowired
private JodaDocRepository jodaDocRepository
def "save document with DateTime"() {
given:
def jodaDoc = new JodaDoc(DateTime.now())
when:
def savedJodaDoc = jodaDocRepository.save(jodaDoc)
then:
savedJodaDoc.id
}
}
It works fine. But when #Configuration annotation in AppConfig is removed/commented:
//#Configuration
#ComponentScan
#EnableMongoRepositories
public class AppConfig extends AbstractMongoConfiguration { ... }
the test fails with:
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type org.joda.time.DateTime to type java.util.Date
AFAIK it is not needed to use #Configuration for the configuration class when it is explicit registered in the context (by classes in #ContextConfiguration or a register() method in AnnotationConfigWebApplicationContext). The classes are processed anyway and all declared beans are found. It is sometimes useful to not use #Configuration to prevent detecting by a component scan when there are 2 similar configuration classes in the same packages in a test context used by different tests.
Therefor I think it could a bug in Spring which causes to different internal beans processing in the context depending on an usage or not a #Configuration annotation. I compared Spring logs from these two cases and there are some differences, but I'm not able to determine what are they caused by in the Spring internal classes. Before a bug submission I would like to ask:
My question. Is there an explicable reason why Spring for the same configuration class (pointed explicit in #ContextConfiguration) uses (or not) converters for Joda-Time depending on an existence of a #Configuration annotation?
I created also a quickstart project reproducing the issue. spring-data-mongodb 1.3.3, spring 4.0.0, joda-time 2.3.
It's everything OK in this behaviour. AbstractMongoConfiguration is annotated by #Configuration, but in fact this annotation is not #Inherited, so you have to explicitly annotate your class.
When you remove #Configuration annotation then your AppConfig class is not a full configuration. It's processes as a lite configuration just because it contains methods annotated by #Bean - please refer to methods in org.springframework.context.annotation.ConfigurationClassUtils
isFullConfigurationCandidate()
isLiteConfigurationCandidate()
isFullConfigurationClass()
Finally only full (annotated by #Configuration) configuration classes are processes and enhanced by configuration post processors - look at ConfigurationClassPostProcessor.enhanceConfigurationClasses()

Categories

Resources