{"record":{"id":"00edbe7cce1af5aa","repo":"airbnb/epoxy","slug":"you-cannot-call-buildmodels-directly-call-setdata-instead-to-00edbe","errorCode":null,"errorMessage":"You cannot call `buildModels` directly. Call `setData` instead to trigger a model refresh with new data.","messagePattern":"You cannot call `buildModels` directly\\. Call `setData` instead to trigger a model refresh with new data\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/Typed4EpoxyController.java","lineNumber":78,"sourceCode":"    allowModelBuildRequests = true;\n    super.moveModel(fromPosition, toPosition);\n    allowModelBuildRequests = false;\n  }\n\n  @Override\n  public void requestDelayedModelBuild(int delayMs) {\n    if (!allowModelBuildRequests) {\n      throw new IllegalStateException(\n          \"You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a \"\n              + \"model refresh with new data.\");\n    }\n    super.requestDelayedModelBuild(delayMs);\n  }\n\n  @Override\n  protected final void buildModels() {\n    if (!isBuildingModels()) {\n      throw new IllegalStateException(\n          \"You cannot call `buildModels` directly. Call `setData` instead to trigger a model \"\n              + \"refresh with new data.\");\n    }\n    buildModels(data1, data2, data3, data4);\n  }\n\n  protected abstract void buildModels(T data1, U data2, V data3, W data4);\n}\n\n","sourceCodeStart":60,"sourceCodeEnd":88,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/Typed4EpoxyController.java#L60-L88","documentation":"Typed4EpoxyController implements buildModels() as final and only allows it to run while an internal model build (started by setData) is in progress. Calling it yourself from outside that flow throws this IllegalStateException. It exists to force users to supply data via setData instead of building models manually.","triggerScenarios":"Calling controller.buildModels() directly on a Typed4EpoxyController subclass from application code, so isBuildingModels() is false at that moment.","commonSituations":"Migrating code from a plain EpoxyController (where buildModels is the override point) to a TypedEpoxyController and keeping an explicit buildModels() call; calling buildModels after updating data manually instead of using setData.","solutions":["Replace the direct buildModels() call with controller.setData(d1, d2, d3, d4)","Move your data update so the controller's setData overload is the single entry point that triggers the rebuild","If a delayed build is needed, use setData with the typed data rather than requestDelayedModelBuild"],"exampleFix":"// before\ncontroller.buildModels();\n// after\ncontroller.setData(data1, data2, data3, data4);","handlingStrategy":"validation","validationCode":"if (controller is Typed4EpoxyController) { controller.setData(d1, d2, d3, d4); } else { controller.buildModels(); }","typeGuard":"fun isTyped4Controller(c: EpoxyController) = c is Typed4EpoxyController<*,*,*,*>","tryCatchPattern":null,"preventionTips":["Never call buildModels on typed controllers; expose only setData in your wrappers","Centralize model rebuild triggers in one helper","Review usages of buildModels when migrating from base EpoxyController"],"tags":["android","epoxy","illegal-state","api-misuse"],"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"}