RowSet
javax.sql.RowSet extends ResultSet with JavaBean-style properties and event-listener support, and its most commonly used subtype, CachedRowSet, additionally disconnects entirely from the database after populating itself — holding all retrieved rows in memory and allowing the underlying Connection to be closed while the data remains accessible, modifiable, and even reconnectable for writing changes back later. This makes RowSet useful for scenarios where a result set's data needs to outlive the connection that produced it, or needs to be passed across layers of an application without dragging a live database connection along with it. This entry covers the RowSet interface family, CachedRowSet's disconnected operation model, and the practical tradeoffs versus simply copying ResultSet data into plain Java objects.
The RowSet Interface Family
import javax.sql.RowSetFactory;
import javax.sql.RowSetProvider;
import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.JdbcRowSet;
public class RowSetFamilyOverview {
public static void main(String[] args) throws Exception {
RowSetFactory factory = RowSetProvider.newFactory();
// JdbcRowSet — stays connected, JavaBean-style configuration + event listeners
JdbcRowSet jdbcRowSet = factory.createJdbcRowSet();
jdbcRowSet.setUrl("jdbc:postgresql://localhost:5432/storedb");
jdbcRowSet.setUsername("app_user");
jdbcRowSet.setPassword("secret");
jdbcRowSet.setCommand("SELECT id, name, price FROM products WHERE category = ?");
jdbcRowSet.setString(1, "electronics");
jdbcRowSet.execute();
while (jdbcRowSet.next()) {
System.out.println(jdbcRowSet.getString("name"));
}
jdbcRowSet.close();
// CachedRowSet — the commonly used disconnected variant, covered in depth below
CachedRowSet cachedRowSet = factory.createCachedRowSet();
cachedRowSet.setUrl("jdbc:postgresql://localhost:5432/storedb");
cachedRowSet.setUsername("app_user");
cachedRowSet.setPassword("secret");
cachedRowSet.setCommand("SELECT id, name, price FROM products WHERE category = ?");
cachedRowSet.setString(1, "electronics");
cachedRowSet.execute(); // connects, runs the query, populates in-memory rows, disconnects
// at this point the underlying database connection is already closed
}
}CachedRowSet's Disconnected Operation Model
import javax.sql.RowSetFactory;
import javax.sql.RowSetProvider;
import javax.sql.rowset.CachedRowSet;
import java.sql.Connection;
import java.sql.DriverManager;
public class CachedRowSetDisconnectedExample {
public static CachedRowSet fetchProductsDisconnected(String category) throws Exception {
RowSetFactory factory = RowSetProvider.newFactory();
CachedRowSet rowSet = factory.createCachedRowSet();
rowSet.setUrl("jdbc:postgresql://localhost:5432/storedb");
rowSet.setUsername("app_user");
rowSet.setPassword("secret");
rowSet.setCommand("SELECT id, name, price FROM products WHERE category = ?");
rowSet.setString(1, category);
rowSet.execute();
// The connection used above is already closed by the time execute() returns.
// 'rowSet' can now be passed to other code, held for a while, or even
// serialized (CachedRowSet implements Serializable) without any live
// database connection being held open in the meantime.
return rowSet;
}
public static void modifyAndWriteBack(CachedRowSet rowSet) throws Exception {
// Edits are tracked in memory only, against the disconnected row set
if (rowSet.next()) {
rowSet.updateBigDecimal("price", new java.math.BigDecimal("24.99"));
rowSet.updateRow();
}
// Writing changes back requires reconnecting — either implicitly, using
// the rowSet's own stored connection parameters, or explicitly:
try (Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/storedb", "app_user", "secret")) {
rowSet.acceptChanges(conn);
// Conflicts with changes made by other transactions since the original
// disconnect are possible here and should be anticipated and handled.
}
}
}RowSet vs. Plain Data Transfer Objects — Practical Tradeoffs
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class PlainObjectAlternativeToRowSet {
record Product(long id, String name, java.math.BigDecimal price) {}
// ── Plain-object approach: copy rows out, close the connection promptly ──
public static List<Product> fetchProducts(Connection conn, String category) throws SQLException {
String sql = "SELECT id, name, price FROM products WHERE category = ?";
List<Product> results = new ArrayList<>();
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, category);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
results.add(new Product(
rs.getLong("id"),
rs.getString("name"),
rs.getBigDecimal("price")));
}
}
}
// Connection can be closed here (e.g. by the caller's try-with-resources) —
// 'results' no longer depends on it at all, same disconnected benefit as
// CachedRowSet, but using a simple, type-safe, purpose-built Java object.
return results;
}
public static void main(String[] args) throws Exception {
try (Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/storedb", "app_user", "secret")) {
List<Product> products = fetchProducts(conn, "electronics");
for (Product p : products) {
System.out.println(p.name() + ": $" + p.price());
}
}
}
}