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 | Show 5 more comments3 Answers
Reset to default 0There are several things wrong with your code.
- Your query needs a whitespace before the
VALUES
element to have a proper query. - 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. List.of
while throw aNullPointerException
if an element isnull
. So don't useList.of
. TheJdbcClient
has aparam
method that simply supports varargs, so no need to wrap things in aList
.
@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.
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 theVALUES
statement. That is wrong, they should have the same count. – M. Deinum Commented Jan 20 at 6:31@Valid
as @M.Deinum points out, you also need to annotate the controller class with@Validated
. – Roar S. Commented Jan 20 at 10:56@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