Background
I have a data class
@Data
public class Data {
@lombok.NonNull
private String name;
}
a controller
@MessageMapping("/data")
public void handleData(@Validated Data data) throws Exception {
if (data.getName().compareTo("Alice") == 0) {
logger.info("Alice is here!");
}
}
and a bean to config jackson to convert booleans to integers (True -> 1, False -> 0)
@Bean
ObjectMapper registerObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("MyBoolSerializer");
module.addSerializer(Boolean.class, new MyBoolSerializer());
module.addDeserializer(Boolean.class, new MyBoolDeserializer());
module.addSerializer(boolean.class, new MyBoolSerializer());
module.addDeserializer(boolean.class, new MyBoolDeserializer());
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return mapper;
}
When I make a request to /data
without setting the name
parameter, Jackson will set it to null
. However, I get the following exception (unwrapped)
org.springframework.messaging.converter.MessageConversionException:
Could not read JSON: Cannot construct instance of `com.example.myapp.entity.Data`
(no Creators, like default constructor, exist):
cannot deserialize from Object value (no delegate- or property-based Creator)
Attempted fix
So then I added @NoArgsConstructor
to Data
.
@Data
@NoArgsConstructor // <<<<
public class Data {
@lombok.NonNull
private String name;
}
Now request to /data
will result in NullPointerException
. The parameters are NOT null-checked and the if-statement
is run.
I tried to use hibernate-validator
's @NotNull
annotation to the name
attribute in Data, but the result is the same: NPE
.
Question
What I thought about the @NonNull
and @NotNull
annotations is that they help validating the data so that I don't need to manually validate them in controllers(check null, check within range, etc.). However it seems to be only valid if the default constructor does not exist. It makes sense because null-checks are not performed in default constructor (no data to validate...).
But then it contradicts with the exception I encountered.
Info that might help
I have amqp
enabled and it has its own MessageConverter
bean that returns a new Jackson2JsonMessageConverter
instance.
import org.springframework.amqp.support.converter.MessageConverter;
@Bean
MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
Any thoughts?
BTW, the title might be a bit ambiguous or misleading, but I genuinely have no idea what the problem is.
--- Edit 1: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>18</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.4.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-reactor-netty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>