Spring boot jackson non_null property not working - spring-boot

I am using Spring boot 1.5.7-RELEASE.
I am trying to set global non null Jackson property in my application.
but it is not working.
I have tried both in application.properties and bootstrap.properties but not working.
spring.jackson.default-property-inclusion=NON_NULL
spring.jackson.serialization-inclusion=NON_NULL
but when I applied on class level, it is working fine.
#JsonInclude(JsonInclude.NON_NULL)

According to the documentation the correct answer is:
spring.jackson.default-property-inclusion=non_null
(note the lowercase non_null - this may be the cause of your problem)
Edit:
I've created a simple Spring Boot 1.5.7.RELEASE project with only the following two compile dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
Then I added the following controller and response classes (using Lombok to skip some boilerplate code):
#RestController
#RequestMapping("/jackson")
public class JacksonTestController {
#GetMapping("/test")
public Response test() {
val response = new Response();
response.setField1("");
return response;
}
}
#Data
class Response {
private String field1;
private String field2;
private Integer field3;
}
Finally I configured Jackson as per documentation, run the application and navigated to http://localhost:8080/jackson/test. The result was (as expected):
{"field1":""}
After that I dug into Spring Boot's source code and discovered that Spring uses class org.springframework.http.converter.json.Jackson2ObjectMapperBuilder to create instances of com.fasterxml.jackson.databind.ObjectMapper. I then put a breakpoint in method public <T extends ObjectMapper> T build() of aforementioned builder class and run my application in debug mode.
I discovered that there are 8 instances of ObjectMapper created during application startup and only one of them is configured using contents of application.properties file. The OP never specified how exactly he was using the serialization, so it's possible his code referred to one of the other 7 object mappers available.
At any rate, the only way to ensure that all object mappers in the application are configured to serialize only non-null properties is to create one's own copy of class org.springframework.http.converter.json.Jackson2ObjectMapperBuilder and etiher hard code that option as default or customize the class to read application.properties during every call to it's constructor or build method.

Maybe I'm late to the party but It may help someone.
Extend WebMvcConfigurationSupport class and customize the Springboot configuration the way you want.
#Configuration
public class Config extends WebMvcConfigurationSupport{
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
converter.setObjectMapper(mapper);
converters.add(converter);
super.configureMessageConverters(converters);
}
}

I was just dealing with the settings in application.properties not taking either. In my case, there was an abstract config class I was extending which defined an ObjectMapper bean that had totally different settings. So I had to override it.
What brought me to the place of finding that was using the /beans Actuator endpoint that Spring Boot apps have, and searching for 'ObjectMapper'. It revealed an instance I didn't realize was being created.

Related

Spring boot validation CGLIB enhanced controller components not autowired

I have created a spring boot application (user spring-boot-starter-parent V. 2.2.2.RELEASE) with Rest controllers, that work fine, now I have added a dependency to :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
And have added #Validated on my controller class so that all methods in it should be validated:
#RestController
#Validated
public class UserController {
#Autowired
private UserService userService;
#PostConstruct
public void init() {
System.out.println("test");
}
}
Now the controller methods when called start throwing NullPointerExceptions because the userService is null; I did a #PostConstruct as a test. Apparently it is called on a normal unenhanced bean which has the fields autowired correctly. But when calling the controller through HTTP, this unenhanced bean is not called, but it is a bean of class UserController$$EnhancerBySpringCGLIB$$ and it has the userController not autowired. I don't really know why cause this is supposed to be really simple, there is not much to configure as far as I know. So I guess for some reason spring does not inject dependencies to CGLIB enhanced classes or just injects it into a wrong class. When I remove #Validated, everything is working fine again, but there is no validation of course.
This was the most ridiculous error I had for ages. I mistakenly set controller methods in my original project as private. This caused the CGLIB to not enhance them and instead just have them with original code. Spring did not complain and is happy to run those methods
Taking your supplied code and making a dummy UserService class, HTTP requests work fine in other environments.
To help solve the issue you're having, you should create a new project, adding the lombok, starter-web, and starter-validation dependencies. Create the following classes.
#RestController
#Validated
public class UserController {
#Autowired
private UserService userService;
#PostConstruct
public void init() {
System.out.println("test");
}
#GetMapping("/test")
public int test(){
return userService.getAge();
}
}
#Data
#Service
public class UserService {
private int age = 21;
}
Then test http://localhost:8080/test
If this doesn't work for you, then you should try invalidating your cache and restarting your IDE. Intellij has an option for that if you click on File > Invalidate Caches / Restart .... Alternatively, you can delete the artifacts (or the full local repo) from your .m2 folder. C:\Users\<username>\.m2\repository and rebuild with maven.
If this still doesn't solve your issue, please update your question with a more complete reproducible example of your problem along with a full console log of your error, and I will update my answer.

Spring Boot 2.1.4: #Autowired does not work in custom Jackson Serializers/Deserializers, how to enable it?

I am struggeling to get a Spring Component #Autowired into my custom Deserializer.
Example:
#JsonDeserialize (using = SomeClassJsonDeserializer.class)
SomeClass {
[...]
}
#JsonComponent
SomeClassJsonDeserializer extends JsonDeserializer<SomeClass> {
#Autowired
private SomeService service;
#Override
public SomeClass deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
[...]
// this.service is null
}
}
I found mainly two possible solutions which didn't work for me at all:
use SpringBeanAutowiringSupport in default constructor of Deserializer
use HandlerInstantiator (via config / custom implementation)
I am using only those Jackson annotations shown in the example above to 'configure' the Jackson parsing.
There is no additional custom configuration affecting Jackson in any way besides the default SpringBoot auto configuration. When using #EnableWebMvc (which breaks Spring-Boot auto configuration so I don't want to use it), the Component-wiring does work as expected.
Is there any official / recommended solution for plain Spring-Boot with default auto configuration ?
The problem was with how I used Spring's RestTemplate.
For a remote call, I created a new Instance of RestTemplate by contructor call (new RestTemplate()).
This way, Spring wasn't able to configure the RestTemplate - bean correctly (so that SpringBoot autoconfigure and Jackson autoconfigure 'connect' together, resulting in working Spring-DI in custom Jackson components).
I simply had to #Autowire the RestTemplateBuilder bean instance provided by Spring, and then call RestTemplateBuilder.build() to aqquire a RestTemplate bean instance created by Spring.

Configuring auto-serialization of Guava Multimap with Spring Boot when the `SpringBootApplication` impelments `WebMvcConfigurer`

I have the following SpringBootApplication:
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#SpringBootApplication
#Configuration
public class RestServer implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(RestServer.class, args);
}
}
And I would like Guava's Multimap get automatically serialized to JSON.
So I have this code in one of my classes, where all other fields are successfully serialized to JSON:
#JsonProperty("multimap_test")
public Multimap<Instrument, AdjustedLimitOrder> getMultimap() {
return someMultiMap;
}
But the returned JSON field looks like this:
"multimap_test": {
"empty": false
}
Do I have to specify a deserializer on the field?
Using this doesn't work because there is no default constructor:
#JsonProperty("multimap_test")
public Multimap<Instrument, AdjustedLimitOrder> getMultimap() {
return someMultimap;
}
Note that my example is implementing WebMvcConfigurer.
Here is a list of links of instructions that didn't seem conclusive on how to do this, because they discuss the case when the SpringBootApplication implements SpringBootServletInitializer or WebMvcConfigurerAdapter (which is deprecated).
http://gdpotter.com/2017/05/24/custom-spring-mvc-jackson/
Enable json serialization of Multimap in Spring Boot Project
How can Guava's Multimap get automatically serialized?
The spring boot MVC auto-configuration already configures to use Jackson to input/output JSON if Jackson is found in classpath. But the configured Jackson by default will not support Guava Multimap, you have to register GuavaModule to the Jackson ObjectMapper in order to support it (see docs).
From the JacksonAutoConfiguration docs , it will auto-register GuavaModule to the ObjectMapper if we declare it as a bean. So in order to serialize/desearialize Multimap , you have to
Add Jackson 's Guava module to pom.xml :
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
</dependency>
Declare a GuavaModule bean in #SpringBootApplication :
#Bean
public Module guavaModule() {
return new GuavaModule();
}
And it is done . WebMvcConfigurer / WebMvcConfigurerAdapter is intended for customizing spring boot MVC default settings . You don't need to customize it for your case as Spring MVC already work with Jackson to input/output JSON out of the box . What you need to do is to configure Jackson but not configure spring boot MVC . So, it does not matter whether SpringBootApplication implement or not implement WebMvcConfigurer/WebMvcConfigurerAdapter if you just want to auto-serialise Multimap.

How to Inject custom method argument in Spring WebFlux using HandlerMethodArgumentResolver?

I want to create an custom method argument Resolver using Spring WebFlux. I am following link but its seem to be not working.
I am able to create the custom argument resolver using WebMvc.
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyCustomeObject.class.isAssignableFrom(parameter.getParameterType())
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
.....
return Mono.just(new MyCustomeObject())
}
Please note that i am using HandlerMethodArgumentResolver from .web.reactive. package.
My AutoConfiguration file look like
#Configuration
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the class-path
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//checks that the app is a reactive web-app
public class RandomWebFluxConfig implements WebFluxConfigurer {
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
MyContextArgumentResolver[] myContextArgumentResolverArray = {contextArgumentResolver()};
configurer.addCustomResolver(myContextArgumentResolverArray );
}
#Bean
public MyContextArgumentResolver contextArgumentResolver() {
return new MyContextArgumentResolver ();
}
My spring.factories looks like
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.XXXX.XXX.XXX.RandomWebFluxConfig
Please note that above configuration is part of the jar which is added in Spring WebFlux Boot project enabled using #EnableWebFlux .
It seems you're conflating two different problems here.
First, you should make sure that your method argument resolver works in a regular project.
For that, you need a #Configuration class that implements the relevant method in WebFluxConfigurer. Your code snippet is doing that but with two flaws:
Your configuration is using #EnableWebFlux, which is disabling the WebFlux auto-configuration in Spring Boot. You should remove that
it seems you're trying to cast a list of MethodArgumentResolver into a single instance and that's probably why things aren't working here. I believe your code snippet could be just:
configurer.addCustomResolver(contextArgumentResolver());
Now the second part of this question is about setting this up as a Spring Boot auto-configuration. I guess that you'd like WebFlux applications to automatically get that custom argument resolvers if they depend on your library.
If you want to achieve that, you should first make sure to read up a bit about auto-configurations in the reference documentation. After that, you'll realize that your configuration class is not really an auto-configuration since it will be applied in all cases.
You should probably add a few conditions on that configuration like:
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) // checks that the app is a reactive web app

Springboot - Thymeleaf tries to resolve files it should not

Hi I am currently working on a web project that uses thymeleaf and also JSF (its a legacy system and we can only slowly migrate to thymeleaf thats why JSF is still there and cannot be removed from one day to another since this is a lot of work). Thymeleaf is configured to resolve the views in the webapp directory that lie under the directory "thymeleaf". This works perfectly if I deploy the application directly on a tomcat server. Also pages from other directories then the "thymeleaf" directory are also resolved by the JSF framework.
I added some integration tests in JUnit that are using SpringBoot. Inside these tests I got the problem that thymeleaf now is trying to resolve any page in any directory. JSF is completely ignored and I got a whole bunch of JUnit tests failing because of that. Is there any point why thymeleaf ignores its configuration and wants to resolve all files?
Here is my complete thymeleaf configuration, and as I said this works perfectly if I deploy it on a standalone tomcat.
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
String imagesPattern = "/images/**";
String imagesLocation = basePath() + "resources/images/";
registry.addResourceHandler(imagesPattern).addResourceLocations(imagesLocation);
log.info("added resourceHandler (pathPattern: '{}'), (resourceLocation: '{}')",
imagesPattern,
imagesLocation);
String cssPattern = "/css/**";
String cssLocation = basePath() + "resources/css/";
registry.addResourceHandler(cssPattern).addResourceLocations(cssLocation);
log.info("added resourceHandler (pathPattern: '{}'), (resourceLocation: '{}')", cssPattern, cssLocation);
}
#Bean(name = "basepath")
public String basePath()
{
String basepath = "";
File file = new File(Optional.ofNullable(System.getenv("THYMELEAF_APP_RESOURCES"))
.orElse("thymeleaf-resources/"));
if (file.exists())
{
basepath = "file:" + file.getAbsolutePath();
}
if (!basepath.endsWith("/"))
{
basepath += "/";
}
log.info("basepath: {}", basepath);
return basepath;
}
#Bean
#Description("Thymeleaf View Resolver")
public ThymeleafViewResolver viewResolver(String basePath)
{
log.info("setting up Thymeleaf view resolver");
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine(basePath));
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setCache(true);
return viewResolver;
}
public SpringTemplateEngine templateEngine(String basePath)
{
log.info("setting up Thymeleaf template engine.");
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver(basePath));
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
private ITemplateResolver templateResolver(String basePath)
{
log.info("setting up Thymeleaf template resolver");
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix(basePath + "thymeleaf/views/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(false);
return resolver;
}
#Bean
public IMessageResolver thymeleafMessageSource(MessageSource messageSource)
{
SpringMessageResolver springMessageResolver = new SpringMessageResolver();
springMessageResolver.setMessageSource(messageSource);
return springMessageResolver;
}
EDIT
I just found that the problem seems to lie much deeper. Having the dependencies of thymeleaf added into my pom.xml seems to be enough for spring boot to load it into the context... I just deleted my ThymeleafConfig class for testing purposes and still thymeleaf tries to resolve the JSF pages... (yes I did maven clean before executing the test)
EDIT 2
I read it now and tried to exclude the ThymeleafAutoConfiguration class but it does not help. My configurations are still overridden. Here is my configuration for this so far. (And yes this is the ONLY EnableAutoConfiguration annotation in the whole project)
#Configuration
#EnableAutoConfiguration(exclude = {ThymeleafAutoConfiguration.class})
#Import({WebAppConfig.class, ThymeleafConfig.class})
public class SpringBootInitializer extends SpringBootServletInitializer
and my ThymeleafConfig class is already added above.
Having the dependencies of thymeleaf added into my pom.xml seems to be enough for spring boot to load it into the context...
If this has surprised you then I would recommend spending some time to take a step back and read about how Spring Boot works and, in particular, it's auto-configuration feature. This section of the reference documentation is a good place to start.
In short, Spring Boot adopts a convention over configuration approach. If a dependency is on the classpath, Spring Boot assumes that you want to use it, and configures it with sensible defaults. This is what it's doing with Thymeleaf. You can disable this auto-configuration for a specific dependency using the excludes attribute on #SpringBootApplication:
#SpringBootApplication(exclude={ThymeleafAutoConfiguration.class})
public class ExampleApplication {
}
You can also use the spring.autoconfigure.exclude property to provide a comma-separated list of auto-configuration classes to exclude. Each entry in the list should be the fully-qualified name of an auto-configuration class. You could use this property with #TestPropertySource to disable auto-configuration on a test-by-test basis.
I have been struggling with a similar issue for hours and finally found out the root cause.
If you have a dependency to *-data-rest in your pom like this:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
you will have to add Thymeleaf to your pom as well even if you use a another template engine (Freemarker, JSP, ...) everywhere else.
Reason: to expose a JpaRepository as a rest service Spring Boot requires Thymeleaf. I do not understand why this is not defined as a dependency of spring-boot-starter-data-rest so that Maven resolves it automatically.
In my opinion it is a Spring Boot configuration bug.

Resources