Quick answer: The most common cause is a component type mismatch in the query definition. EntityQuery requires exact type matches -- if your entity has IComponentData struct 'Health' but your query asks for a different 'Health' type (wrong namespace, wrong assembly), the query returns nothing.
Here is how to fix Unity dots entity query returning empty. You create entities, attach components, build an EntityQuery, and iterate — but the loop body never executes. The query returns zero results. The entities exist (you can see them in the Entity Debugger), the components are there, and there are no errors in the console. This is one of the most frustrating DOTS debugging experiences because the type system gives you no compile-time warning when a query will silently match nothing. Here is how to diagnose and fix it.
The Symptom
You have a system that should process entities with specific components. You define an EntityQuery either through GetEntityQuery, SystemAPI.QueryBuilder, or the foreach shorthand in SystemBase. At runtime, query.CalculateEntityCount() returns zero. Your OnUpdate method runs, but the inner loop over entities is empty.
Opening the Entity Debugger shows that entities with the expected components do exist. They are alive, they have the right component types attached, and their data looks correct. But the system cannot see them. It is as if the query and the entities exist in parallel universes.
You might try adding a debug log inside OnUpdate to confirm the system is running. It is. You log the entity count from the query. It is zero. You create a second query with fewer constraints. Suddenly it matches. The problem is somewhere in the query definition, but which part?
What Causes This
There are four primary causes for empty entity queries, and they are deceptively hard to spot because none of them produce errors or warnings.
1. Component type mismatch. This is the most common cause and the hardest to debug. DOTS uses exact type matching for queries. If you have two structs both named Health in different namespaces or assemblies, they are completely different component types to the ECS. Your entities might have Game.Components.Health while your query asks for Game.Systems.Health. The names look identical in quick code reviews, but the types are different and the query matches nothing.
2. Overly restrictive query filters. The WithAll, WithAny, and WithNone constraints are combined with AND logic. If your WithNone list includes a component that happens to be on every entity (like LocalTransform or LocalToWorld), the query excludes everything. This is especially common when you add WithNone<Disabled> to filter out disabled entities but accidentally include another type in the same constraint that is universally present.
3. Structural changes destroying or modifying entities before the query runs. If another system removes a required component or destroys entities earlier in the frame, your query will find nothing by the time it executes. This is particularly subtle when EntityCommandBuffer playback happens at a sync point between your creation system and your query system, and the command buffer includes a RemoveComponent call you forgot about.
4. Querying before entity creation. If your system runs in the InitializationSystemGroup or very early in the SimulationSystemGroup, entities created by bakers or other initialization systems may not exist yet. System ordering in DOTS is not always intuitive, and a system that visually appears "after" another in the Inspector may actually execute before it.
The Fix
Step 1: Verify component types match exactly. Add a diagnostic method that inspects entity archetypes at runtime and compares them against your query's requirements.
using Unity.Entities;
using Unity.Collections;
using UnityEngine;
public partial class QueryDiagnosticSystem : SystemBase
{
protected override void OnUpdate()
{
var query = GetEntityQuery(
ComponentType.ReadOnly<Health>(),
ComponentType.ReadOnly<EnemyTag>()
);
int count = query.CalculateEntityCount();
Debug.Log($"Query matched {count} entities");
if (count == 0)
{
// Dump all archetypes to find where our entities actually are
var allEntities = EntityManager.GetAllEntities(Allocator.Temp);
foreach (var entity in allEntities)
{
var archetype = EntityManager.GetChunk(entity).Archetype;
var types = archetype.GetComponentTypes();
var typeNames = new System.Text.StringBuilder();
foreach (var t in types)
typeNames.Append(t.ToString()).Append(", ");
Debug.Log($"Entity {entity.Index}: [{typeNames}]");
}
allEntities.Dispose();
}
}
}
This diagnostic dumps every entity's component list to the console. Look for your expected component types. If you see Game.Components.Health in the entity archetypes but your query uses a different Health type, you have found the mismatch. Fix it by using fully qualified type names or consolidating duplicate types into a single shared assembly.
Step 2: Audit your query filters. Build the query explicitly and log each constraint to verify nothing unexpected is filtering out your entities.
using Unity.Entities;
using UnityEngine;
public partial class EnemyDamageSystem : SystemBase
{
private EntityQuery _enemyQuery;
protected override void OnCreate()
{
// Build the query explicitly so each constraint is visible
_enemyQuery = GetEntityQuery(new EntityQueryDesc
{
All = new ComponentType[]
{
ComponentType.ReadOnly<Health>(),
ComponentType.ReadOnly<EnemyTag>(),
ComponentType.ReadWrite<DamageBuffer>()
},
None = new ComponentType[]
{
ComponentType.ReadOnly<Dead>()
}
});
// Log the query description for debugging
Debug.Log($"EnemyDamageSystem query: " +
$"All=[Health, EnemyTag, DamageBuffer] " +
$"None=[Dead]");
}
protected override void OnUpdate()
{
int count = _enemyQuery.CalculateEntityCount();
if (count == 0)
{
Debug.LogWarning("EnemyDamageSystem: zero entities matched. " +
"Check that enemies have Health, EnemyTag, and " +
"DamageBuffer, and do NOT have Dead.");
return;
}
// Process matched entities
Entities
.WithStoreEntityQueryInField(ref _enemyQuery)
.ForEach((Entity entity, ref Health health,
in DamageBuffer damage) =>
{
health.Current -= damage.Amount;
})
.Schedule();
}
}
Pay special attention to the None array. A common mistake is adding WithNone<LocalTransform>() when you meant to filter by a game-specific tag. Since nearly every entity has LocalTransform, this silently empties the query. Remove components from None one at a time to isolate which filter is causing the empty result.
Step 3: Control system execution order to avoid structural change conflicts. If entities are being modified or destroyed before your query runs, use explicit system ordering to guarantee your system executes at the right time.
using Unity.Entities;
using Unity.Burst;
using UnityEngine;
// Ensure this system runs AFTER the spawning system
[UpdateAfter(typeof(EnemySpawnSystem))]
// Ensure this system runs BEFORE the cleanup system
[UpdateBefore(typeof(DeadEntityCleanupSystem))]
public partial struct EnemyProcessingSystem : ISystem
{
private EntityQuery _query;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_query = state.GetEntityQuery(
ComponentType.ReadOnly<Health>(),
ComponentType.ReadOnly<EnemyTag>()
);
// Require at least one matching entity before OnUpdate runs
state.RequireForUpdate(_query);
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// This only runs when _query has at least one match
foreach (var (health, entity) in
SystemAPI.Query<RefRW<Health>>()
.WithAll<EnemyTag>()
.WithEntityAccess())
{
if (health.ValueRO.Current <= 0)
{
// Use ECB to defer structural changes to end of frame
var ecb = SystemAPI
.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>()
.CreateCommandBuffer(state.WorldUnmanaged);
ecb.AddComponent<Dead>(entity);
}
}
}
}
The [UpdateAfter] and [UpdateBefore] attributes enforce execution order within a system group. The RequireForUpdate call prevents the system from running at all when the query is empty, which avoids wasted work and makes it easier to distinguish between "the system did not run" and "the system ran but found nothing."
Using EndSimulationEntityCommandBufferSystem for structural changes ensures that component additions and removals happen at the end of the simulation frame, after all gameplay systems have had a chance to query the current state. This prevents one system's structural changes from invalidating another system's queries mid-frame.
Why This Works
The diagnostic approach works because DOTS entity archetypes are fully introspectable at runtime. By dumping the actual component types on each entity, you can compare them character-by-character against the types in your query. This catches namespace mismatches, assembly boundary issues, and cases where a baker adds a different component type than you expect.
Explicit query construction with EntityQueryDesc works because it makes every constraint visible in one place. When you use the Entities.ForEach lambda syntax or SystemAPI.Query, the actual query filters are inferred from the lambda signature and chained method calls. This is convenient but makes it easy to accidentally include a filter you did not intend. Building the query manually in OnCreate forces you to enumerate every type constraint explicitly.
System ordering attributes work because DOTS systems within a group execute in a deterministic order. Without ordering constraints, systems run in an undefined order that may change between editor sessions or builds. By making the order explicit, you guarantee that entities created by one system are visible to the next, and that structural changes from cleanup systems do not run until after all gameplay queries have completed.
SystemBase vs ISystem
The query API differs slightly between SystemBase (managed, class-based) and ISystem (unmanaged, struct-based). In SystemBase, you call GetEntityQuery on the system itself and use Entities.ForEach for iteration. In ISystem, you call state.GetEntityQuery and use SystemAPI.Query for iteration.
A subtle pitfall: if you mix SystemBase query patterns inside an ISystem, the code may compile but produce empty queries. The Entities.ForEach API is not available in ISystem and using it through workarounds can result in queries that never match. Stick to SystemAPI.Query in ISystem implementations and Entities.ForEach in SystemBase implementations.
Edge Cases to Watch For
Shared component filters. If your query uses WithSharedComponentFilter, remember that it filters by value equality. If the shared component value on your entities does not exactly match the filter value (including default-constructed fields), the query returns empty. This is especially tricky with shared components that contain enum fields or reference types.
Enableable components. DOTS supports enableable components via IEnableableComponent. When a component is disabled on an entity, queries with that component in WithAll will still match the entity by default. However, if you use WithOptions(EntityQueryOptions.IgnoreComponentEnabledState) or the component is in WithNone, the enabled state matters. Verify whether your components implement IEnableableComponent and whether their enabled state is what you expect.
World mismatch. If you create entities in one World but your system runs in a different World, the query will find nothing. This happens more often than you might expect when working with sub-scenes, the default world, and custom worlds in server/client architectures with Netcode for Entities.
"When a DOTS query returns empty, resist the urge to rewrite the query. Dump the archetypes first. The entities will tell you exactly what they have — trust the data over your assumptions."
Related Issues
If your query matches entities but the data is stale or zero-initialized, the problem may be with your baker or component initialization rather than the query itself. For systems that intermittently find entities and then lose them, look for structural changes that add and remove tag components in a ping-pong pattern across frames. And if your queries work in the editor but fail in builds, check that all component types are in assemblies that survive code stripping — Unity's managed code linker can remove types it thinks are unused, breaking DOTS queries that reference them only through generic type parameters.
Dump the archetypes. The entities never lie.