greenrobot/greenDAO · error · IllegalStateException

Offset cannot be set without limit

Error message

Offset cannot be set without limit

What it means

SQL requires that OFFSET only appears together with LIMIT. QueryBuilder.checkAddOffset() enforces this when building the SQL: if offset was set via offset(offset) but limit was never set, an IllegalStateException is thrown. The check exists because the generated SQL 'OFFSET ?' without LIMIT is invalid in SQLite.

Solutions

  1. Always call .limit(n) before or after .offset(n) on the QueryBuilder.
  2. Compute page size from your paging constant instead of leaving limit unset.
  3. If you truly want 'skip first N', combine offset(N).limit(aLargeNumber) since SQLite needs a LIMIT.

Example fix

// before
List<User> users = userDao.queryBuilder().offset(20).list(); // IllegalStateException
// after
List<User> users = userDao.queryBuilder().limit(10).offset(20).list();
Defensive patterns

Strategy: validation

Validate before calling

QueryBuilder<User> qb = userDao.queryBuilder();
if (qb.build().getSql() != null && offsetSet && limitNotSet) throw new IllegalArgumentException("set limit before offset");

Try / catch

try { list = qb.list(); } catch (IllegalStateException e) { list = qb.limit(pageSize).offset(offset).list(); }

Prevention

When it happens

Trigger: queryBuilder.offset(10).list() or build() without calling limit() first — offsetPosition() / checkAddOffset during SQL construction.

Common situations: Pagination code that sets offset but forgets the page size (limit), often after refactoring where the limit call was removed or moved behind a conditional.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of greenrobot/greenDAO@0bbb338e17 (2026-09-08). Data as JSON: /api/errors/efde13b9805f3b08. Report an issue: GitHub.

Appendix: source

Thrown at DaoCore/src/main/java/org/greenrobot/greendao/query/QueryBuilder.java:333

        }
        return builder;
    }

    private int checkAddLimit(StringBuilder builder) {
        int limitPosition = -1;
        if (limit != null) {
            builder.append(" LIMIT ?");
            values.add(limit);
            limitPosition = values.size() - 1;
        }
        return limitPosition;
    }

    private int checkAddOffset(StringBuilder builder) {
        int offsetPosition = -1;
        if (offset != null) {
            if (limit == null) {
                throw new IllegalStateException("Offset cannot be set without limit");
            }
            builder.append(" OFFSET ?");
            values.add(offset);
            offsetPosition = values.size() - 1;
        }
        return offsetPosition;
    }

    /**
     * Builds a reusable query object for deletion (Query objects can be executed more efficiently than creating a
     * QueryBuilder for each execution.
     */
    public DeleteQuery<T> buildDelete() {
        if (!joins.isEmpty()) {
            throw new DaoException("JOINs are not supported for DELETE queries");
        }
        String tablename = dao.getTablename();
        String baseSql = SqlUtils.createSqlDelete(tablename, null);

View on GitHub (pinned to 0bbb338e17)