最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How do I allow null values in a POST request body? Java and Spring Framework - Stack Overflow

programmeradmin3浏览0评论

I have a VideoGame record:

public record VideoGame(
        @Positive
        Integer id,
        @NotEmpty
        String title,
        @NotEmpty
        String console,
        @NotEmpty
        String genre,
        String publisher,
        String developer,
        Double critic_score,
        Double other_sales,
        LocalDate release_date,
        LocalDate last_update
) { }

A VideoGame repository:

@Repository
public class VideoGameRepository {

    private final JdbcClient jdbcClient;

    public VideoGameRepository(JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }

    public void create(VideoGame videoGame) {
        var newVideoGame = jdbcClient.sql("INSERT INTO videogamesalestest (id, title, console, genre, publisher, developer, critic_score, release_date, last_update)" +
                "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
                .param(List.of(videoGame.id(), videoGame.title(), videoGame.console(), videoGame.genre(), videoGame.publisher(), videoGame.developer(), videoGame.critic_score(), videoGame.release_date(), videoGame.last_update()))
                .update();

        Assert.state(newVideoGame == 1, "Failed to insert new videoGame");
    }
}

and a controller for this:

@RestController
@RequestMapping("/api/videogames")
public class VideoGameController {

    private final VideoGameRepository videoGameRepository;

    public VideoGameController(VideoGameRepository videoGameRepository) {
        this.videoGameRepository = videoGameRepository;
    }

    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping("")
    void create(@RequestBody VideoGame videoGame) {
        videoGameRepository.create(videoGame);
    }
}

I want to allow the client to submit a POST request that may or may not contain all of the fields of a VideoGame record.

I tried just setting the field value to null in the JSON body as such, and then sending the request:

{
  "id": 1,
  "title": "NewGame",
  "console": "Xbox",
  "genre": "Action",
  "publisher": "CBGAMES",
  "developer": "CBGAMES",
  "critic_score": 10.0,
  "release_date": "2025-01-07",
  "last_update": null
}

but I get a status 500 response "Internal Server Error".

I've also tried annotating the fields in the record with @Nullable, but I get a warning in the repository that the argument might be null.

What can I do to acheive this? Will I need to make a class instead of a record to handle null values or is there a more "Springy" way to accomplish. I also considered using Optionals, but I've read that this isn't really what Optional were meant acheive. I also don't want to restrict the client from passing in a complete record, since it doesn't make sense in my case.

Update: My question has been resolved. The core issue I was facing (ignoring the syntactical problems with my code) was how do I allow null values, or lack of values, be passed from my request body and deserialized into a VideoGame object. The solution was to remove the call to List.of() in my params() call. The List.of() API clearly states it throws a NullPointException if an element is null. Removing the List.of() and just passing the params was the fix. Interestingly, I didn't even need to @Nullable the record fields, and I didn't need @Valid in my controller.

I have a VideoGame record:

public record VideoGame(
        @Positive
        Integer id,
        @NotEmpty
        String title,
        @NotEmpty
        String console,
        @NotEmpty
        String genre,
        String publisher,
        String developer,
        Double critic_score,
        Double other_sales,
        LocalDate release_date,
        LocalDate last_update
) { }

A VideoGame repository:

@Repository
public class VideoGameRepository {

    private final JdbcClient jdbcClient;

    public VideoGameRepository(JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }

    public void create(VideoGame videoGame) {
        var newVideoGame = jdbcClient.sql("INSERT INTO videogamesalestest (id, title, console, genre, publisher, developer, critic_score, release_date, last_update)" +
                "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
                .param(List.of(videoGame.id(), videoGame.title(), videoGame.console(), videoGame.genre(), videoGame.publisher(), videoGame.developer(), videoGame.critic_score(), videoGame.release_date(), videoGame.last_update()))
                .update();

        Assert.state(newVideoGame == 1, "Failed to insert new videoGame");
    }
}

and a controller for this:

@RestController
@RequestMapping("/api/videogames")
public class VideoGameController {

    private final VideoGameRepository videoGameRepository;

    public VideoGameController(VideoGameRepository videoGameRepository) {
        this.videoGameRepository = videoGameRepository;
    }

    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping("")
    void create(@RequestBody VideoGame videoGame) {
        videoGameRepository.create(videoGame);
    }
}

I want to allow the client to submit a POST request that may or may not contain all of the fields of a VideoGame record.

I tried just setting the field value to null in the JSON body as such, and then sending the request:

{
  "id": 1,
  "title": "NewGame",
  "console": "Xbox",
  "genre": "Action",
  "publisher": "CBGAMES",
  "developer": "CBGAMES",
  "critic_score": 10.0,
  "release_date": "2025-01-07",
  "last_update": null
}

but I get a status 500 response "Internal Server Error".

I've also tried annotating the fields in the record with @Nullable, but I get a warning in the repository that the argument might be null.

What can I do to acheive this? Will I need to make a class instead of a record to handle null values or is there a more "Springy" way to accomplish. I also considered using Optionals, but I've read that this isn't really what Optional were meant acheive. I also don't want to restrict the client from passing in a complete record, since it doesn't make sense in my case.

Update: My question has been resolved. The core issue I was facing (ignoring the syntactical problems with my code) was how do I allow null values, or lack of values, be passed from my request body and deserialized into a VideoGame object. The solution was to remove the call to List.of() in my params() call. The List.of() API clearly states it throws a NullPointException if an element is null. Removing the List.of() and just passing the params was the fix. Interestingly, I didn't even need to @Nullable the record fields, and I didn't need @Valid in my controller.

Share Improve this question edited Jan 20 at 17:21 Cody B asked Jan 20 at 0:50 Cody BCody B 32 bronze badges 10
  • 1 For starters your query is wrong. There should be a space before the VALUES. Another thing is your validation isn't working as you don't have an @Valid on the @RequestBody as well. Finally you are sending more parameters in the request then you have elements in your constructor. Finally 2: Your query is also wrong, you have defined 9 columns to insert but are adding 14 elements in the VALUES statement. That is wrong, they should have the same count. – M. Deinum Commented Jan 20 at 6:31
  • Hello friends just adding up what @M.Deinum, try reading and understand the logging message that your application produce and share it here if you need help to understand the logging message when you get "Internal Server Error" that should help you get started to what you must trace. – Aleson Commented Jan 20 at 7:45
  • And when adding @Valid as @M.Deinum points out, you also need to annotate the controller class with @Validated. – Roar S. Commented Jan 20 at 10:56
  • @RoarS. No you don't need the @Validated on the class, don't even do that. That is for a totally different use case (and as of Spring 6.2 not even needed anymore as well)! – M. Deinum Commented Jan 20 at 12:21
  • @M.Deinum: Thanks, I just tested without Validated-annotation on one of my controllers, and tests started to fail. I will look further into this. I'm using Spring Boot 3.4.1. – Roar S. Commented Jan 20 at 12:29
 |  Show 5 more comments

3 Answers 3

Reset to default 0

There are several things wrong with your code.

  1. Your query needs a whitespace before the VALUES element to have a proper query.
  2. Your query identifies 9 columns in the INSERT clause, while providing 14 placeholders, those numbers should match. So either add additional columns or remove placeholders.
  3. List.of while throw a NullPointerException if an element is null. So don't use List.of. The JdbcClient has a param method that simply supports varargs, so no need to wrap things in a List.
@Repository
public class VideoGameRepository {

    private final JdbcClient jdbcClient;

    public VideoGameRepository(JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }

    public void create(VideoGame videoGame) {
        var newVideoGame = jdbcClient.sql("INSERT INTO videogamesalestest (id, title, console, genre, publisher, developer, critic_score, release_date, last_update) " +
                "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)")
                .param(videoGame.id(), videoGame.title(), videoGame.console(), videoGame.genre(), videoGame.publisher(), videoGame.developer(), videoGame.critic_score(), videoGame.release_date(), videoGame.last_update())
                .update();

        Assert.state(newVideoGame == 1, "Failed to insert new videoGame");
    }
}

Something like the above should make things work. It has an additional after the INSERT ... stuff. The number of placeholders has been reduced and it now uses the param method with var-args instead of List.of.

The problem is that such factory methods like List.of and others do not tolerate nulls. One of the possible ways to solve the problem is to use Arrays.asList() instead:

    List<?> params = Arrays.asList(
            game.title(),
            game.console(),
            game.genre(),
            game.criticScore(),
            game.releaseDate(),
            game.lastUpdate()
    );

    client.sql(<your_sql>)
            .params(params)
            .update();

you know, records do not allow null values by default, making it less suitable to use record types directly in such cases.

You can use a regular Java class instead of a record, so that you can manually handle null values. In the class, all fields can be of the Optional type, or you can keep them as their primitive types but allow them to be null.

发布评论

评论列表(0)

  1. 暂无评论