Quick answer: The address string you pass to LoadAssetAsync must exactly match the address set in the Addressables Groups window — including case. Rebuild the catalog after adding assets, and use LoadResourceLocationsAsync to validate a key before loading.
You open your game, hit a loading screen, and the console lights up with UnityEngine.AddressableAssets.InvalidKeyException: Exception of type ‘UnityEngine.AddressableAssets.InvalidKeyException’ was thrown. No Asset found with for key: enemies/goblin. The asset definitely exists in your project. The Addressables group is right there in the window. Yet the runtime can’t find it. This post walks through every common cause and the correct fix for each.
The Symptom
The exception surfaces in one of two forms depending on your Unity version. In Unity 2021 and earlier you see a raw KeyNotFoundException. From Unity 2022 onward, the Addressables package wraps it into a more descriptive InvalidKeyException that includes the key string. Either way, the AsyncOperationHandle.Status is Failed and AsyncOperationHandle.OperationException holds the exception object.
The stack trace typically looks like this:
InvalidKeyException: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException'
was thrown. No Asset found with for key: enemies/goblin
UnityEngine.AddressableAssets.Addressables.InternalLoad ...
UnityEngine.AddressableAssets.Addressables.LoadAssetAsync ...
EnemySpawner.SpawnEnemy () (at Assets/Scripts/EnemySpawner.cs:42)
What Causes This
1. The address string doesn’t match exactly
The most common cause is a mismatch between the string you pass in code and the address shown in the Addressables Groups window. Addressables keys are case-sensitive. "enemies/Goblin" and "enemies/goblin" are two different keys. Open the Groups window (Window › Asset Management › Addressables › Groups), select the asset, and read the address from the inspector. Copy it verbatim.
It is easy to accidentally use the asset’s filename (without path) as the address, but the default address Unity assigns is the full project-relative path, for example Assets/Prefabs/Enemies/Goblin.prefab. If you renamed the address to a shorter slug like enemies/goblin you must use that slug in code, not the original path.
2. Confusing labels with addresses
Addressables supports two kinds of keys: addresses (a unique string per asset) and labels (a tag shared by many assets). LoadAssetAsync<T> works with either, but if you pass a label that does not exist you get the same InvalidKeyException. Labels are managed separately in Window › Asset Management › Addressables › Labels. Double-check that the label is spelled correctly and is actually applied to the asset you want.
3. Catalog not rebuilt after adding assets
In a player build, Addressables ships a binary catalog (catalog_YYYY.MM.DD.HH.MM.SS.hash and its accompanying .json). This catalog is generated at build time. If you add a new asset to a group after building the player, the player’s catalog doesn’t know about it.
To regenerate the catalog, go to Window › Asset Management › Addressables › Groups and choose Build › New Build › Default Build Script. Then rebuild the player. In the Unity Editor using Play Mode Script: Use Asset Database (fastest), assets are resolved directly from the project database, so you won’t see this issue in Editor play mode — which makes it a painful one to catch before shipping.
4. AssetReference vs. string address
An AssetReference field in the Inspector stores a GUID-based reference, not a string. When you call assetRef.LoadAssetAsync<T>() it resolves using the GUID, bypassing the address string entirely. String-based loading and AssetReference loading are independent. If your AssetReference field is null (not assigned in the Inspector), you’ll get a null-key exception instead:
// This will throw if assetRef is not assigned in the Inspector
[SerializeField] private AssetReferenceGameObject assetRef;
async void Start()
{
// Safe check before loading
if (assetRef == null || !assetRef.RuntimeKeyIsValid())
{
Debug.LogError("AssetReference is not assigned or invalid.");
return;
}
var handle = assetRef.LoadAssetAsync<GameObject>();
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
Instantiate(handle.Result);
}
The Fix
Validate the key before loading
Use Addressables.LoadResourceLocationsAsync to check whether a key resolves to any locations before you attempt the actual load. This is especially useful in loading screens or systems where the key comes from data (a level config file, a remote server response, etc.) rather than a hardcoded string.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
public class SafeAddressableLoader : MonoBehaviour
{
public async System.Threading.Tasks.Task<T> LoadSafe<T>(string key)
where T : class
{
// Step 1: check the key exists in the catalog
var locationHandle =
Addressables.LoadResourceLocationsAsync(key, typeof(T));
await locationHandle.Task;
if (locationHandle.Status != AsyncOperationStatus.Succeeded
|| locationHandle.Result == null
|| locationHandle.Result.Count == 0)
{
Debug.LogWarning($"Addressables key not found: {key}");
Addressables.Release(locationHandle);
return null;
}
Addressables.Release(locationHandle); // release location handle, not the asset
// Step 2: load the asset
var assetHandle = Addressables.LoadAssetAsync<T>(key);
await assetHandle.Task;
if (assetHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"Failed to load asset: {key}\n"
+ assetHandle.OperationException);
Addressables.Release(assetHandle);
return null;
}
return assetHandle.Result;
}
}
Catch the exception asynchronously
If you don’t want to pre-validate every key, at minimum wrap the load in proper async error handling. Swallowing the exception silently is worse — log it with enough context to reproduce later:
private async void LoadEnemy(string enemyKey)
{
var handle = Addressables.LoadAssetAsync<GameObject>(enemyKey);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Failed)
{
// OperationException holds the InvalidKeyException
Debug.LogError(
$"[EnemySpawner] Could not load '{enemyKey}': "
+ handle.OperationException?.Message);
Addressables.Release(handle);
return;
}
Instantiate(handle.Result, spawnPoint.position, Quaternion.identity);
// Remember to Release when the instance is destroyed
}
Centralise address strings
Scattered string literals are a maintenance trap. Define addresses in a single static class so a rename in the Addressables window requires only one code change:
public static class AddressKeys
{
public static class Enemies
{
public const string Goblin = "enemies/goblin";
public const string Orc = "enemies/orc";
public const string Dragon = "enemies/dragon";
}
public static class Scenes
{
public const string MainMenu = "scenes/main-menu";
public const string Level1 = "scenes/level-01";
}
}
Related Issues
If the key resolves correctly but the asset still fails to load, check for these related problems:
- Remote catalog not downloaded yet. If you use remote hosting,
Addressables.InitializeAsync()must complete before any load. In loading screens, await initialization explicitly. - Bundle CRC mismatch. If you updated remote bundles but the local catalog cache is stale, loads fail with a hash mismatch error. Clear the cache with
Caching.ClearAllCachedVersions(bundleName)during development. - Wrong group build path. Each Addressables group has a Build Path and Load Path. If Build Path is set to a remote path but the bundles are only deployed locally (or vice versa), the catalog will list assets that the runtime can’t actually fetch.
- Scene not in Addressable group.
LoadSceneAsyncwith a string key requires the scene asset to be in an Addressables group. Adding the scene to Build Settings is not enough.
When Addressables key errors show up in player builds but not in Editor, add the key validation step — it catches catalog drift before your players do.