airbnb/epoxy · error · IllegalEpoxyUsage
This model was already added to the controller at position
Error message
This model was already added to the controller at position {position} What it means
Each model instance can be added to a controller only once, because ids must be unique for diffing. When debug validation is enabled and the model is detected a second time in the controller's building list, Epoxy throws IllegalEpoxyUsage naming the first position it was added.
Solutions
- Create a new model instance for each item (e.g. in a loop: new ItemModel_().id(item.getId()))
- Remove the duplicate add of the same instance
- Give reused models distinct ids and build them fresh each pass
- Keep model construction inside buildModels instead of caching shared instances
Example fix
// before
ItemModel_ item = new ItemModel_();
for (Data d : list) { item.id(d.id); item.add(); } // same instance twice
// after
for (Data d : list) { new ItemModel_().id(d.id).addTo(this); } Defensive patterns
Strategy: validation
Validate before calling
if (buildingModels.contains(model)) throw new IllegalStateException("Model added twice: " + model); Try / catch
try { buildModelsInternal(); } catch (IllegalEpoxyUsage e) { Log.e(TAG, "Duplicate model add", e); } Prevention
- Create a new model instance per item in loops
- Never reuse a single model field for multiple list entries
- Build models fresh each buildModels pass
When it happens
Trigger: Adding the same EpoxyModel instance twice in buildModels (e.g. appending it to the list twice, or reusing one instance variable for multiple list items).
Common situations: Reusing a single field model for each item of a list instead of creating a new instance per item; accidentally calling add() on the model twice; adding a model to two lists that both get built.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- You must set an id on a model before adding it. Use the…
- Controller cannot be null
- Epoxy attribute fields on a model cannot be changed once…
- Models cannot be empty
- Must have stable ids when saving view holder state
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/626f1103139a7b14.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModel.java:418
/**
* @see #addIf(AddPredicate, EpoxyController)
*/
public interface AddPredicate {
boolean addIf();
}
/**
* This is used internally by generated models to turn on validation checking when
* "validateEpoxyModelUsage" is enabled and the model is used with an {@link EpoxyController}.
*/
protected final void addWithDebugValidation(@NonNull EpoxyController controller) {
if (controller == null) {
throw new IllegalArgumentException("Controller cannot be null");
}
if (controller.isModelAddedMultipleTimes(this)) {
throw new IllegalEpoxyUsage(
"This model was already added to the controller at position "
+ controller.getFirstIndexOfModelInBuildingList(this));
}
if (firstControllerAddedTo == null) {
firstControllerAddedTo = controller;
// We save the current hashCode so we can compare it to the hashCode at later points in time
// in order to validate that it doesn't change and enforce mutability.
hashCodeWhenAdded = hashCode();
// The one time it is valid to change the model is during an interceptor callback. To support
// that we need to update the hashCode after interceptors have been run.
// The model can be added to multiple controllers, but we only allow an interceptor change
// the first time, since after that it will have been added to an adapter.
controller.addAfterInterceptorCallback(new ModelInterceptorCallback() {
@Override
public void onInterceptorsStarted(EpoxyController controller) {View on GitHub (pinned to e45bd3a61f)