{"record":{"id":"efde13b9805f3b08","repo":"greenrobot/greenDAO","slug":"offset-cannot-be-set-without-limit","errorCode":null,"errorMessage":"Offset cannot be set without limit","messagePattern":"Offset cannot be set without limit","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"DaoCore/src/main/java/org/greenrobot/greendao/query/QueryBuilder.java","lineNumber":333,"sourceCode":"        }\n        return builder;\n    }\n\n    private int checkAddLimit(StringBuilder builder) {\n        int limitPosition = -1;\n        if (limit != null) {\n            builder.append(\" LIMIT ?\");\n            values.add(limit);\n            limitPosition = values.size() - 1;\n        }\n        return limitPosition;\n    }\n\n    private int checkAddOffset(StringBuilder builder) {\n        int offsetPosition = -1;\n        if (offset != null) {\n            if (limit == null) {\n                throw new IllegalStateException(\"Offset cannot be set without limit\");\n            }\n            builder.append(\" OFFSET ?\");\n            values.add(offset);\n            offsetPosition = values.size() - 1;\n        }\n        return offsetPosition;\n    }\n\n    /**\n     * Builds a reusable query object for deletion (Query objects can be executed more efficiently than creating a\n     * QueryBuilder for each execution.\n     */\n    public DeleteQuery<T> buildDelete() {\n        if (!joins.isEmpty()) {\n            throw new DaoException(\"JOINs are not supported for DELETE queries\");\n        }\n        String tablename = dao.getTablename();\n        String baseSql = SqlUtils.createSqlDelete(tablename, null);","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/greenrobot/greenDAO/blob/0bbb338e17c4cf28aba5aae62d42f73f50186157/DaoCore/src/main/java/org/greenrobot/greendao/query/QueryBuilder.java#L315-L351","documentation":"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.","triggerScenarios":"queryBuilder.offset(10).list() or build() without calling limit() first — offsetPosition() / checkAddOffset during SQL construction.","commonSituations":"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.","solutions":["Always call .limit(n) before or after .offset(n) on the QueryBuilder.","Compute page size from your paging constant instead of leaving limit unset.","If you truly want 'skip first N', combine offset(N).limit(aLargeNumber) since SQLite needs a LIMIT."],"exampleFix":"// before\nList<User> users = userDao.queryBuilder().offset(20).list(); // IllegalStateException\n// after\nList<User> users = userDao.queryBuilder().limit(10).offset(20).list();","handlingStrategy":"validation","validationCode":"QueryBuilder<User> qb = userDao.queryBuilder();\nif (qb.build().getSql() != null && offsetSet && limitNotSet) throw new IllegalArgumentException(\"set limit before offset\");","typeGuard":null,"tryCatchPattern":"try { list = qb.list(); } catch (IllegalStateException e) { list = qb.limit(pageSize).offset(offset).list(); }","preventionTips":["Centralize paging in a helper that always sets limit+offset together.","Never call offset() without a page-size constant.","Write a unit test asserting generated SQL contains both LIMIT and OFFSET."],"tags":["greendao","querybuilder","sql","android"],"backgroundTag":"missing-required-argument","analyzedSha":"0bbb338e17c4cf28aba5aae62d42f73f50186157","analyzedAt":"2026-09-08T03:22:36.050Z","contentChangedAt":"2026-09-08T03:22:36.050Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}