# 6. The JDBC writer, and why it is not `beanMapped()` [← Previous](05-launching-and-jobparameters.md) | [README](../README.md) | [Next: Restartability →](07-restartability.md) `JdbcBatchItemWriterBuilder` offers two ways to map an item's fields to SQL parameters: `columnMapped()` (positional, via `Map`/`SqlParameterSource` keyed by column name) and `beanMapped()` (reflective, via `BeanPropertySqlParameterSource`, matching `:paramName` markers in the SQL to JavaBean getters). Most Spring Batch examples reach for `beanMapped()` because it needs the least code. `Product` in this module is a Java record: ```java public record Product(String sku, String name, long priceCents) {} ``` Its accessors are `sku()`, `name()`, `priceCents()` — no `get` prefix. Standard JavaBean introspection, which `BeanPropertySqlParameterSource` uses, looks for `getSku()`, `getName()`, `getPriceCents()`. Those do not exist on a record, so `beanMapped()` against a plain record either finds nothing to bind (leaving every parameter `NULL`) or fails outright, depending on the exact introspector version in play — not a mistake you want to discover from a table full of NULLs in production. [`BatchConfig.productWriter`](../src/main/java/com/ankurm/batch/config/BatchConfig.java) sidesteps the question entirely with `itemPreparedStatementSetter`, setting each column explicitly: ```java new JdbcBatchItemWriterBuilder() .dataSource(jdbcTemplate.getDataSource()) .sql("INSERT INTO PRODUCT (sku, name, price_cents) VALUES (?, ?, ?)") .itemPreparedStatementSetter((item, ps) -> { ps.setString(1, item.sku()); ps.setString(2, item.name()); ps.setLong(3, item.priceCents()); }) .assertUpdates(true) .build(); ``` More typing, zero ambiguity about what gets bound where, and it works identically whether the item type is a record, a plain class, or something with no JavaBean getters at all.
If you want beanMapped() with records anyway. Newer versions of Spring's BeanWrapperImpl (the machinery behind BeanPropertySqlParameterSource) have gained some record support in recent Spring Framework releases, but the safe rule is to check it against the exact Spring Framework version you are on rather than assume — the failure mode when it does not work is silent NULLs, not an exception, which is the worst kind of wrong.
`assertUpdates(true)` is worth keeping on deliberately: it makes the writer throw if a batch statement reports zero rows updated for any item, instead of silently accepting a no-op write. Combined with `itemPreparedStatementSetter`, a typo in a column name fails loudly at the first write attempt rather than producing a table that looks plausible but is missing a column's worth of data. ## Going deeper - `JdbcBatchItemWriter` and its two mapping styles: [Spring Batch reference — item writers](https://docs.spring.io/spring-batch/reference/readers-and-writers/item-writer.html) (`rel="nofollow"`). - Java records and JavaBean introspection generally: this is not a Spring Batch quirk, it is how `java.beans.Introspector` has always worked, and it bites anywhere a library assumes `getX()`/`setX()` — Jackson, JPA, validation frameworks — each with its own answer for how much (if any) record support it has added. [Next: Restartability →](07-restartability.md)