The underlying issue here is that JDBC distinguishes between updates (SQL statements which return just the number of affected rows) and queries (SQL statements returning a set of rows, which end up in a ResultSet), and allows batching (i.e. sending multiple statements to the server at once) only for updates, not for queries.
INSERT ... RETURNING ...
belongs to the "query" category, and thus can't be batched.
A possible workaround is mentioned in this answer to "Insert batch and Return full object using NamedParameterJdbcTemplate" – it's basically constructing a new SQL statement with many lines in the VALUES clause for each call.
If you are calling this with batches of many different sizes, your DB optimizer will see many different statements, and might fail to optimize it properly (or at least, has to compile it again each time).
I found this old post Batching Select Statement with JDBC, which elaborates several options here, and suggests an approach named there "select batching", where prepared statements of several sizes are set up, and then sent several of them to the DB with the data.
Because I just also had this problem, I've taken this approach, used spring's NamedParameterJdbcTemplate and build a generic class which puts together these queries based on some template strings.
Full code is in this PR I just opened (to my library which needs it).
It can then be used like this (using the example from the question):
QueryStatementBatcher<Integer> batcher = new QueryStatementBatcher<>(
"INSERT INTO users (name) VALUES ", "(:name#)", " RETURNING id",
(row, n) -> row.getInt("id"));
Stream<MapSqlParameterSource> inputs =
Stream.of("Paŭlo Ebermann", "jagadesh", "Harun Cetin")
.map(name -> new MapSqlParameterSource()
.addValue("name#", name));
List<Integer> generatedIds =
batcher.queryForStream(jdbcTemplate, inputs)
.collect(toList());
Of course, you'd create the batcher only once and reuse it (it doesn't have mutable state, so it is thread-safe, and computing the templates is a bit of effort at the beginning).