prestodb/presto · error · SQLFeatureNotSupportedException

Result set concurrency must be CONCUR_READ_ONLY

Error message

Result set concurrency must be CONCUR_READ_ONLY

What it means

PrestoConnection.checkResultSet() throws SQLFeatureNotSupportedException when resultSetConcurrency is not ResultSet.CONCUR_READ_ONLY. Presto result sets are read-only streams; updatable cursors (CONCUR_UPDATABLE) would require write-back to the result source, which the protocol does not support. Note this message is only reached when the type check at line 894 already passed.

Source

Thrown at presto-jdbc/src/main/java/com/facebook/presto/jdbc/PrestoConnection.java:897

            throw new SQLException("Connection is closed");
        }
    }

    private void initializeQueryInterceptors()
    {
        for (QueryInterceptor interceptor : this.queryInterceptorInstances) {
            interceptor.init(this.sessionProperties);
        }
    }

    private static void checkResultSet(int resultSetType, int resultSetConcurrency)
            throws SQLFeatureNotSupportedException
    {
        if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
            throw new SQLFeatureNotSupportedException("Result set type must be TYPE_FORWARD_ONLY");
        }
        if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) {
            throw new SQLFeatureNotSupportedException("Result set concurrency must be CONCUR_READ_ONLY");
        }
    }

    private static void checkHoldability(int resultSetHoldability)
            throws SQLFeatureNotSupportedException
    {
        if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
            throw new SQLFeatureNotSupportedException("Result set holdability must be HOLD_CURSORS_OVER_COMMIT");
        }
    }

    private static String getIsolationLevel(int level)
            throws SQLException
    {
        switch (level) {
            case TRANSACTION_READ_UNCOMMITTED:
                return "READ UNCOMMITTED";
            case TRANSACTION_READ_COMMITTED:

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Pass ResultSet.CONCUR_READ_ONLY as the concurrency argument
  2. Perform modifications with explicit UPDATE/INSERT/DELETE SQL statements instead of ResultSet.updateRow()
  3. Use prepareStatement(sql) defaults which are read-only

Example fix

// before
Statement stmt = conn.createStatement(
    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
// after
Statement stmt = conn.createStatement(
    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
Defensive patterns

Strategy: validation

Validate before calling

if (concurrency != ResultSet.CONCUR_READ_ONLY) {
    concurrency = ResultSet.CONCUR_READ_ONLY; // coerce before calling driver
}

Try / catch

try {
    stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, concurrency);
} catch (SQLFeatureNotSupportedException e) {
    stmt = conn.createStatement();
}

Prevention

When it happens

Trigger: createStatement(anyType, ResultSet.CONCUR_UPDATABLE) or prepareStatement(sql, type, ResultSet.CONCUR_UPDATABLE) with a forward-only type.

Common situations: Framework-generated JDBC code that requests updatable result sets to allow row updates; porting legacy CRUD code that relied on updatable cursors from MySQL/PostgreSQL.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/759318a35110e4c7. Report an issue: GitHub.