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.
Related
My problem references to this issue here on github https://github.com/mapstruct/mapstruct/issues/1427
I got at least two versions of mappers with the same name. I want to use springs getBean/Autowired possibilities but this doesn't work out of the mapstructs box yet. :-)
I followed the second workaround mentioned in the upper link: extend Springs bean naming strategy. Did someone ever get this well ment proposal working?
If i follow the code parts from there the bean naming doesn't take place. For me its's clear why not: there aren't any components to scan and especially to find.
If i add a componenModel = "spring" to the mapper annotation i get a ConflictingBeanDefinitionException. Don't know why. Maybe there's a cat in the tail problem?
As stated from Filip here https://github.com/mapstruct/mapstruct/issues/1427 i followed his approach and with a few modifications it worked. I added a solution comment in the link.
The main changes are:
i added componentModel = "spring" to my mappers and used a filter to exclude all of my mapper classes (the interface all of my mappers are implementing: MapperInterface.class) within the Spring Boot application.
To my Spring Boot application class i added:
#ComponentScan(basePackages = { "com.application.spring_boot_class" }, excludeFilters = { #ComponentScan.Filter(value = { MapperInterface.class }, type = FilterType.ASSIGNABLE_TYPE) })
I had this issue before, and I resolved it using the Spring bean definition in a configuration class, a class annotated with #Configuration, with the Mapstruct mapper call like below:
#Bean
public IMyMapper offerWebMapper() {
return Mappers.getMapper(IMyMapper.class);
}
And then you can inject the mapper using #Autowired or getBean.
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
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
In Jersey, how can I automatically add root in JAXB pojo?
I have my JAXB pojo
class Product {
private String name;
....
}
By default the generated json is
{
"name": "Burton Custom Freestlye 151",
}
I would like it to be
{
"product": {
"name": "Burton Custom Freestlye 151",
}
}
Note: What I mean by automatic is not creating separate class just to enclose another pojo.
This should be in serialization/deserialization into JSON.
Also I have other JSON don't use root element.
With Spring Boot, you can just configure an ObjectMapper as a Spring bean, and Spring Boot is set up where Jersey will use the mapper. The configuration property for the ObjectMapper to automatically add a root element is
SerializationFeature.WRAP_ROOT_VALUE
The default behavior is to take the class name and lower case it. If you want something different you can annotation the class with #XmlRootElement("newName") or #JsonRootName("newName"). To make a Spring bean just add the following in your configuration class
#Bean
public ObjectMapper mapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_NAME, true);
return mapper;
}
Note I think this (creating a Spring bean for the ObjectMapper) is only available starting from 1.4.0. If you are using a earlier version of Boot, you should just do this.
Also if you are expecting incoming JSON with the wrapped value, you will also want to unwrap it. You can do that with
DeserializationFeature.UNWRAP_ROOT_VALUE
Configure it the same way as above (passing true).
UPDATE
(Not very useful at this point, but maybe in the future)
Looking at the source for #JsonRootName, it seems Jackson has been planning on adding an alwaysWrap property in the annotation since 2.4. But I guess they have been delaying this. Not sure why. Maybe in the future it'll show up.
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