Search code examples
spring-bootrestjpanullpointerexceptioncrud

Spring Boot CRUD REST API + Spring Data JPA + H2 DB: Getting NPE on tests but not on Postman


I'm working on some exercises to familiarize myself with spring framework and working with REST API's. I did pass the exercise, but not with my original implementation. I'd like to know why I was getting NPE on tests but not on Postman.

The test threw NPE when trying to solve this quiz:

{
  "title": "Math4",
  "text": "Which of the following is equal to 4?",
  "options": ["2*3", "5*8", "8*0", "1*5"],
  "answer": null
}

When I use Postman to post this quiz to the dB and solve, everything works fine.

Post Quiz Solve Quiz

But when I run it through the test I receive the following:

Stacktrace:

java.lang.NullPointerException: null
at engine.presentation.controllers.QuizController.solveQuiz(QuizController.java:44) ~[main/:na]
at jdk.internal.reflect.GeneratedMethodAccessor90.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at jdk.internal.reflect.GeneratedMethodAccessor64.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at java.base/javax.security.auth.Subject.doAsPrivileged(Subject.java:550) ~[na:na]
at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:170) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:225) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:47) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:149) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:145) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.29.jar:9.0.29]
at jdk.internal.reflect.GeneratedMethodAccessor47.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at java.base/javax.security.auth.Subject.doAsPrivileged(Subject.java:550) ~[na:na]
at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:191) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:47) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:149) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:145) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at jdk.internal.reflect.GeneratedMethodAccessor47.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at java.base/javax.security.auth.Subject.doAsPrivileged(Subject.java:550) ~[na:na]
at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:191) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:47) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:149) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:145) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at jdk.internal.reflect.GeneratedMethodAccessor47.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at java.base/javax.security.auth.Subject.doAsPrivileged(Subject.java:550) ~[na:na]
at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:191) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:47) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:149) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:145) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at jdk.internal.reflect.GeneratedMethodAccessor47.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at java.base/javax.security.auth.Subject.doAsPrivileged(Subject.java:550) ~[na:na]
at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:191) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:47) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:149) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:145) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at jdk.internal.reflect.GeneratedMethodAccessor47.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:282) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil$1.run(SecurityUtil.java:279) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at java.base/javax.security.auth.Subject.doAsPrivileged(Subject.java:550) ~[na:na]
at org.apache.catalina.security.SecurityUtil.execute(SecurityUtil.java:314) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.security.SecurityUtil.doAsPrivilege(SecurityUtil.java:253) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:191) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain.access$000(ApplicationFilterChain.java:47) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:149) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:145) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]

build.gradle

plugins {
    id 'org.springframework.boot' version '2.2.2.RELEASE'
    id 'java'
    id "io.freefair.lombok" version "6.1.0-m3"
}

apply plugin: 'io.spring.dependency-management'

sourceCompatibility = 11

repositories {
    mavenCentral()
}

sourceSets.main.resources.srcDirs = ["src/resources"]

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
}

application.settings

server.port=8889
management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true

# H2 data source setup
spring.datasource.url=jdbc:h2:file:../quizdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect


# Automatically update tables when persistence objects have changed
spring.jpa.hibernate.ddl-auto=update

# Setup for the H2 console, used for viewing data in the database
spring.h2.console.enabled=true
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false

# Show SQL statements generated by Spring ORM
spring.jpa.show-sql=true

Quiz.java:

@Entity
@Table(name = "quizzes")
@NoArgsConstructor
@Getter
@Setter
@ToString
@JsonPropertyOrder({
        "id",
        "title",
        "text",
        "options"
})
public class Quiz {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    private int id;

    @NotBlank(message = "Title is required")
    private String title;

    @NotBlank(message = "Text is required")
    private String text;

    @NotEmpty(message = "There must be at least 2 options")
    @Size(min = 2, message = "There must be at least 2 options")
    private LinkedHashSet<String> options;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private HashSet<Integer> answer;

    public void setAnswer(HashSet<Integer> answer) {
        this.answer = Optional.ofNullable(answer).orElseGet(HashSet::new);
    }
}

QuizController.java

@RestController
@RequestMapping("/api")
public class QuizController {

    private final QuizService quizService;

    @Autowired
    public QuizController(QuizService quizService) {
        this.quizService = quizService;
    }

    @GetMapping("/quizzes")
    public Iterable<Quiz> getQuizzes() {
        return quizService.findAll();
    }

    @GetMapping("/quizzes/{id}")
    public Quiz getQuiz(@PathVariable @Min(0) int id) {
        return quizService.findById(id);
    }

    @PostMapping("/quizzes")
    public Quiz saveQuiz(@RequestBody @Valid Quiz quiz) {
        return quizService.save(quiz);
    }

    @PostMapping("/quizzes/{id}/solve")
    public Response solveQuiz(@RequestBody @Valid AnswerDto answer,
                          @PathVariable @Min(0) int id) {
        return Response.builder()
                .success(quizService.findById(id).getAnswer().equals(answer.getAnswer()))
                .build();
    }
}

QuizService.java:

    @Service
public class QuizService {
    private final QuizRepository repository;

    @Autowired
    public QuizService(QuizRepository repository) {
        this.repository = repository;
    }

    public Iterable<Quiz> findAll() {
        return repository.findAll();
    }

    public Quiz findById(int id) {
        Optional<Quiz> quiz = repository.findById(id);

        if (quiz.isEmpty()) {
            throw new QuizNotFoundException(id);
        }

        return quiz.get();
    }

    public Quiz save(Quiz quiz) {
        return repository.save(quiz);
    }
}

What I do not understand is how a Quiz is being created with a null answer field if the setAnswer method doesn't ever set the field to null. From what I understand, and please correct me if I am mistaken. When JSON is deserialized to object with @RequestBody, it uses the default (no argument) constructor and the defined setter methods to instantiate the object. Also, why am I unable to replicate the issue on Postman?

This is just for educational purposes. Even though I solved the problem, I want to understand where I went wrong to begin with. Any help is truly appreciated.

EDIT: For clarification, if a null value gets passed as the answer when creating a Quiz object an empty HashSet is created. This should deserialize to an empty array, as shown in the postman images above. When I post the answer to the quiz, I receive the success response because the answer is [];


Solution

  • The problem is that
    quizService.findById(id).getAnswer().equals(answer.getAnswer())
    

    fails when the first getAnswer() returns null, on which equals cannot be invoked.

    You can use null-safe Objects::equals instead:

    Objects.equals(quizService.findById(id).getAnswer(), answer.getAnswer())
    

    The problem is that the test sends the quiz jsons without an explicit answer field. In that case your setter is not invoked by Jackson. To enforce the logic that is currently in the setter, a constructor with @JsonCreator should help.