{"record":{"id":"023f21391df4300f","repo":"airbnb/epoxy","slug":"cannot-call-requestmodelbuild-from-inside-buildmodels","errorCode":null,"errorMessage":"Cannot call `requestModelBuild` from inside `buildModels`","messagePattern":"Cannot call `requestModelBuild` from inside `buildModels`","errorType":"exception","errorClass":"IllegalEpoxyUsage","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyController.java","lineNumber":155,"sourceCode":"    int DELAYED = 2;\n  }\n\n  /**\n   * Call this to request a model update. The controller will schedule a call to {@link\n   * #buildModels()} so that models can be rebuilt for the current data. Once a build is requested\n   * all subsequent requests are ignored until the model build runs. Therefore, the calling code\n   * need not worry about calling this multiple times in a row.\n   * <p>\n   * The exception is that the first time this is called on a new instance of {@link\n   * EpoxyController} it is run synchronously. This allows state to be restored and the initial view\n   * to be draw quicker.\n   * <p>\n   * If you would like to be alerted when models have finished building use\n   * {@link #addModelBuildListener(OnModelBuildFinishedListener)}\n   */\n  public void requestModelBuild() {\n    if (isBuildingModels()) {\n      throw new IllegalEpoxyUsage(\"Cannot call `requestModelBuild` from inside `buildModels`\");\n    }\n\n    // If it is the first time building models then we do it right away, otherwise we post the call.\n    // We want to do it right away the first time so that scroll position can be restored correctly,\n    // shared element transitions aren't delayed, and content is shown asap. We post later calls\n    // so that they are debounced, and so any updates to data can be completely finished before\n    // the models are built.\n    if (hasBuiltModelsEver) {\n      requestDelayedModelBuild(0);\n    } else {\n      buildModelsRunnable.run();\n    }\n  }\n\n  /**\n   * Whether an update to models is currently pending. This can either be because\n   * {@link #requestModelBuild()} was called, or because models are currently being built or diff\n   * on a background thread.","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyController.java#L137-L173","documentation":"EpoxyController.requestModelBuild() schedules a fresh buildModels() pass, but calling it from inside buildModels() would recursively re-enter model building, so an IllegalEpoxyUsage is thrown. Model building must complete before a new build can be requested.","triggerScenarios":"Calling requestModelBuild() (or APIs that trigger it) inside the buildModels() method of a controller — e.g. in response to observing data, or in a model-build listener invoked during building.","commonSituations":"Calling controller.requestModelBuild() from a data-binding callback fired while models are being built; requesting a rebuild inside an interceptor or in code called from buildModels via a shared update() method.","solutions":["Move the requestModelBuild() call outside buildModels (e.g. in the data-change handler that calls setData)","Defer the request until after building completes (post to the main handler or use addModelBuildListener then request)","If shared code updates data and requests a build, split it so the request happens only when not building (check isBuildingModels())"],"exampleFix":"// before\n@Override\nprotected void buildModels() {\n  requestModelBuild(); // crash\n  ...\n}\n// after\nvoid onDataChanged(Data d) {\n  setData(d);\n  requestModelBuild();\n}","handlingStrategy":"try-catch","validationCode":"if (!controller.isBuildingModels()) { controller.requestModelBuild(); }","typeGuard":null,"tryCatchPattern":"try { controller.requestModelBuild(); } catch (IllegalEpoxyUsage e) { controller.post { -> requestModelBuild after build completes } }","preventionTips":["Never call requestModelBuild from buildModels or code it invokes; use setData/onDataChanged entry points","Use addModelBuildFinishedListener to schedule follow-up builds after completion"],"tags":["android","epoxy","reentrancy","illegal-usage"],"backgroundTag":"invalid-state-transition","analyzedSha":"e45bd3a61fe3a1f130e184f5b8dcf172ab99025a","analyzedAt":"2026-09-13T03:24:16.052Z","contentChangedAt":"2026-09-13T03:24:16.052Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}