Skip to content
Java jdbc 6 min read

RowSet

RowSet is a powerful extension of ResultSet that wraps JDBC operations inside a JavaBeans-style component. It can work disconnected from the database, be serialized and sent over a network, and even fire events when data changes — all things a plain ResultSet cannot do.

Rowset interface hierarchy

Why RowSet Exists

A standard ResultSet is tied to an open database connection for its entire lifetime. That works fine for short server-side queries, but it breaks down when you need to:

  • Pass query results to a UI layer without keeping a connection open.
  • Serialize results and send them over the wire (e.g., in a web service).
  • Filter, sort, or paginate data in memory without hitting the database again.
  • Bind table data directly to a Swing component via the observer pattern.

RowSet was designed to fill all these gaps. It lives in javax.sql (the optional JDBC extension package, included in the standard JDK since Java SE 1.4).

The RowSet Interface Hierarchy

RowSet extends ResultSet, so every method you already know — next(), getString(), getInt(), etc. — still works. On top of that, RowSet adds the JavaBeans contract: properties, a no-arg constructor, and event support.

ResultSet  (java.sql)
   └── RowSet  (javax.sql)
         ├── JdbcRowSet         — connected, scrollable, updatable
         ├── CachedRowSet       — disconnected, serializable
         │      ├── WebRowSet   — CachedRowSet + XML serialization
         │      │      └── FilteredRowSet  — in-memory filtering
         │      └── JoinRowSet  — SQL JOIN across multiple RowSets

The five standard implementations all live in javax.sql.rowset and are part of the JDK (concrete classes are in com.sun.rowset or provided by your JDBC driver).

Creating a RowSet with RowSetProvider

Since Java 7, the recommended way to create a RowSet is through RowSetProvider and RowSetFactory — no need to reference vendor-specific classes:

import javax.sql.rowset.*;

RowSetFactory factory = RowSetProvider.newFactory();
CachedRowSet crs = factory.createCachedRowSet();
JdbcRowSet    jrs = factory.createJdbcRowSet();

Tip: Always use RowSetProvider.newFactory() rather than new com.sun.rowset.CachedRowSetImpl(). The factory picks the best available implementation and keeps your code portable.

JdbcRowSet — Connected and Scrollable

JdbcRowSet is the simplest upgrade from a plain ResultSet. It stays connected, but it is scrollable and updatable by default, and it supports events.

import javax.sql.rowset.*;
import java.sql.*;

public class JdbcRowSetDemo {
    public static void main(String[] args) throws Exception {
        RowSetFactory factory = RowSetProvider.newFactory();

        try (JdbcRowSet jrs = factory.createJdbcRowSet()) {
            jrs.setUrl("jdbc:mysql://localhost:3306/school");
            jrs.setUsername("root");
            jrs.setPassword("password");
            jrs.setCommand("SELECT id, name FROM students WHERE grade > ?");
            jrs.setDouble(1, 75.0);
            jrs.execute();          // opens connection, runs query

            while (jrs.next()) {
                System.out.println(jrs.getInt("id") + " — " + jrs.getString("name"));
            }
        } // connection closed automatically
    }
}

Output:

1 — Alice
3 — Carlos
5 — Priya

Notice that JdbcRowSet manages the connection internally. You just set URL, credentials, command, and parameters — then call execute().

CachedRowSet — Disconnected and Serializable

CachedRowSet is the star of the show. It fetches all rows into memory, closes the connection, and lets you work offline. When you are done editing, you can synchronize changes back to the database in one batch.

import javax.sql.rowset.*;
import java.sql.*;

public class CachedRowSetDemo {
    public static void main(String[] args) throws Exception {
        // --- Phase 1: connect, fetch, disconnect ---
        RowSetFactory factory = RowSetProvider.newFactory();
        CachedRowSet crs = factory.createCachedRowSet();

        crs.setUrl("jdbc:mysql://localhost:3306/school");
        crs.setUsername("root");
        crs.setPassword("password");
        crs.setCommand("SELECT id, name, grade FROM students");
        crs.execute();  // fetches all rows, then closes connection

        // --- Phase 2: work completely offline ---
        while (crs.next()) {
            double grade = crs.getDouble("grade");
            if (grade < 50.0) {
                crs.updateDouble("grade", 50.0); // in-memory update
                crs.updateRow();
            }
        }

        // --- Phase 3: sync changes back ---
        try (Connection conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/school", "root", "password")) {
            crs.acceptChanges(conn); // writes deltas to the database
        }

        crs.close();
    }
}

Note: acceptChanges() uses an optimistic locking strategy. If another transaction modified the same row between your fetch and your sync, a SyncProviderException is thrown. You can plug in a custom SyncProvider to change the conflict-resolution policy.

Pagination with CachedRowSet

For large result sets you can page through data by setting a page size:

crs.setPageSize(50);       // fetch at most 50 rows per page
crs.execute();             // loads first 50 rows, disconnects

// process first page …

crs.nextPage();            // reconnects, fetches rows 51–100, disconnects
// process second page …

This is useful when you cannot load an entire table into memory.

WebRowSet — XML Serialization

WebRowSet extends CachedRowSet and adds the ability to read/write rows as a standardized XML document (defined by the W3C RowSet schema). This is handy for web services and cross-language data exchange.

import javax.sql.rowset.*;
import java.io.*;

WebRowSet wrs = factory.createWebRowSet();
wrs.setUrl("jdbc:mysql://localhost:3306/school");
wrs.setUsername("root");
wrs.setPassword("password");
wrs.setCommand("SELECT id, name FROM students");
wrs.execute();

// Write rows as XML
try (Writer out = new FileWriter("students.xml")) {
    wrs.writeXml(out);
}

// Read rows back from XML (no database needed)
WebRowSet wrs2 = factory.createWebRowSet();
try (Reader in = new FileReader("students.xml")) {
    wrs2.readXml(in);
}
wrs2.close();
wrs.close();

FilteredRowSet — In-Memory Filtering

FilteredRowSet lets you apply a Predicate filter to a CachedRowSet without writing another SQL query:

import javax.sql.rowset.*;
import javax.sql.rowset.predicate.Range;

FilteredRowSet frs = factory.createFilteredRowSet();
frs.setUrl("jdbc:mysql://localhost:3306/school");
frs.setUsername("root");
frs.setPassword("password");
frs.setCommand("SELECT id, name, grade FROM students");
frs.execute();

// Show only students with grade between 80 and 100
Range gradeFilter = new Range(80, 100, "grade");
frs.setFilter(gradeFilter);

while (frs.next()) {
    System.out.println(frs.getString("name") + ": " + frs.getDouble("grade"));
}
frs.close();

Tip: Range is a built-in Predicate in javax.sql.rowset.predicate. You can also implement the Predicate interface yourself for custom logic.

RowSet Events

Every RowSet is a JavaBeans event source. You can listen for row changes, cursor movement, and row set changes:

jrs.addRowSetListener(new RowSetListener() {
    @Override
    public void cursorMoved(RowSetEvent event) {
        System.out.println("Cursor moved");
    }
    @Override
    public void rowChanged(RowSetEvent event) {
        System.out.println("Row changed");
    }
    @Override
    public void rowSetChanged(RowSetEvent event) {
        System.out.println("Entire RowSet reloaded");
    }
});

This makes RowSet a natural fit for data-binding in desktop UIs (e.g., Swing’s TableModel).

RowSet vs ResultSet — Quick Comparison

FeatureResultSetRowSet
Requires open connectionYes (always)Only JdbcRowSet; others disconnect
SerializableNoYes (CachedRowSet and subclasses)
Scrollable by defaultDepends on Statement typeYes
Updatable by defaultDepends on Statement typeYes
JavaBeans eventsNoYes
XML serializationNoYes (WebRowSet)
In-memory filteringNoYes (FilteredRowSet)
SQL JOIN across setsNoYes (JoinRowSet)

Under the Hood

When you call CachedRowSet.execute(), the implementation:

  1. Opens a Connection using the stored URL/credentials (or a DataSource).
  2. Creates a PreparedStatement, sets parameters, and calls executeQuery().
  3. Iterates the ResultSet and copies every row’s data into an internal Object[][] array (the “original” snapshot).
  4. Closes the ResultSet, Statement, and Connection.
  5. Sets a separate “current” copy of the data for in-memory edits.

When you call updateRow() on a disconnected CachedRowSet, changes go into the “current” copy only. The “original” snapshot is untouched. acceptChanges() diffs the two copies to generate minimal INSERT/UPDATE/DELETE statements, reconnects, and executes them. If the database row was modified by someone else since you fetched it (optimistic lock conflict), a SyncProviderException is raised.

Because all rows live in heap memory, CachedRowSet is inappropriate for large result sets — prefer pagination (setPageSize) or server-side filtering in SQL. For connection pooling, pass a DataSource to setDataSource() instead of setting URL/username/password directly, so the pool manages connections efficiently.

  • ResultSet — the foundation interface that RowSet extends
  • PreparedStatement — parameterised SQL execution used internally by RowSet
  • Statement — the basic JDBC statement for running SQL
  • Transaction Management — controlling commits and rollbacks during acceptChanges()
  • Batch Processing — another pattern for batching database writes efficiently
  • JDBC — start here for the full JDBC picture
Last updated June 13, 2026
Was this helpful?