| Keywords | try, catch, finally, throw, when |
|---|---|
| Category | Error handling |
| In Unity | The red errors in the Console: NullReferenceException, MissingReferenceException... |
Exceptions & try/catch
An exception is C#'s way of saying "something went wrong and I can't continue". If nothing handles it, Unity prints a red error in the Console and skips the rest of that method. A try/catch block lets you handle the problem yourself.
A tightrope walker with a safety net. The code inside try is the walk. If the walker falls (an exception), the catch is the net that catches them, so the show can go on. finally is the crew who clean up the stage afterwards, whether the walker made it across or fell.
Syntax
try
{
// Code that might fail
}
catch (SpecificException ex)
{
// Runs only if that kind of exception happened
}
catch (Exception ex)
{
// Catches anything else (put the most general one last)
}
finally
{
// ALWAYS runs: after try succeeds, after a catch, even after a return
}
What Unity does with an unhandled exception
- Your code throws, e.g. you call a method on a
nullvariable inUpdate. - C# stops running that method immediately, and walks back up through the methods that called it, looking for a matching
catch. - If none is found, Unity catches it at the top, logs a red error with the stack trace, and moves on to the next script's
Update. - The game doesn't crash, but everything after the error line in that method was skipped. Next frame it runs again, and probably fails again, which is why you see the same error hundreds of times.
- Inside a coroutine, an unhandled exception stops that coroutine for good.
Bullet.Spawn() throws, C# abandons it and each caller in turn, looking for a matching catch. If none is found, Unity catches it, logs the red Console error, and moves on to the next script, so the rest of Player.Update() is skipped this frame.When to use try/catch in Unity
Use it for things outside your control that can fail even when your code is correct:
- Reading and writing files (disk full, file missing, no permission)
- Parsing data from files, the network or players (bad JSON, bad numbers)
- Web requests and platform APIs
- Calling third-party code or mods you don't trust
Don't use it to hide bugs in your own code. A NullReferenceException from an unassigned field should be fixed, not caught.
Code Samples
Loading a save file safely
using System;
using System.IO;
using UnityEngine;
public static class SaveSystem
{
private static string SavePath => Path.Combine(Application.persistentDataPath, "save.json");
public static SaveData Load()
{
try
{
string json = File.ReadAllText(SavePath);
SaveData data = JsonUtility.FromJson<SaveData>(json);
return data ?? new SaveData(); // FromJson can return null for empty text
}
catch (FileNotFoundException)
{
// First time playing: not an error.
return new SaveData();
}
catch (Exception ex)
{
// Corrupt file or no permission: tell the developer, but keep the game running.
Debug.LogError($"Could not load save: {ex.Message}");
return new SaveData();
}
}
public static void Save(SaveData data)
{
string tempPath = SavePath + ".tmp";
try
{
// Write to a temp file first, so a crash mid-write can't destroy the real save.
File.WriteAllText(tempPath, JsonUtility.ToJson(data));
File.Copy(tempPath, SavePath, overwrite: true);
}
catch (IOException ex)
{
Debug.LogError($"Could not save: {ex.Message}");
}
finally
{
if (File.Exists(tempPath)) File.Delete(tempPath); // clean up either way
}
}
}
[Serializable]
public class SaveData
{
public int level = 1;
public int coins;
}
The ?? here is fine because SaveData is a plain C# class, not a Unity object.
Parsing without exceptions: TryParse
Many .NET types offer a Try... method that returns false instead of throwing. It's faster and cleaner than try/catch.
// Throws FormatException on bad input:
int a = int.Parse(inputField.text);
// Better: no exception, just check the result
if (int.TryParse(inputField.text, out int amount))
{
GiveCoins(amount);
}
else
{
errorLabel.text = "Please enter a number.";
}
Unity's own APIs follow the same idea: TryGetComponent, Dictionary.TryGetValue.
Throwing your own exceptions
Throw when a method is used wrongly in a way the caller must fix.
public void SetVolume(float volume)
{
if (volume < 0f || volume > 1f)
throw new ArgumentOutOfRangeException(nameof(volume), "Volume must be between 0 and 1.");
// ...
}
Keeping one bad listener from breaking the others
foreach (var listener in listeners)
{
try
{
listener.OnGameSaved();
}
catch (Exception ex)
{
Debug.LogException(ex); // logs with the full stack trace, then continues the loop
}
}
Common exceptions in Unity
| Exception | Usually means | Fix |
|---|---|---|
NullReferenceException | You used a variable that's null: an unassigned Inspector field, or GetComponent found nothing | Double-click the error; check which variable on that line is null |
MissingReferenceException | The object was destroyed, but you still use it | Check != null before use; unsubscribe events in OnDisable |
UnassignedReferenceException | A serialized field was never assigned in the Inspector | Assign it, or check in Awake and log a clear error |
IndexOutOfRangeException / ArgumentOutOfRangeException | Array/list index is negative or ≥ Count | Loop with i < array.Length, not <= |
KeyNotFoundException | dictionary[key] for a key that isn't there | Use TryGetValue |
InvalidOperationException | "Collection was modified" while looping over it with foreach; or reading old Input with only the new Input System on | Loop backwards with for; see Handling Input |
StackOverflowException | A method or property calls itself forever | Usually crashes the Editor; check recursive calls and properties |
Common Mistakes
- Empty catch blocks.
catch { }hides the problem completely, so you'll never know why something doesn't work. At minimum,Debug.LogException(ex). - Catching
Exceptionaround everything. Catch the specific exceptions you expect and can handle. - Using exceptions for normal flow. Throwing is slow. "Player has no key" is a normal situation: use an
if, not an exception. throw ex;inside a catch. This resets the stack trace. To re-throw, write justthrow;.- try/catch around a
yield return. C# doesn't allowyield returninside atryblock that has acatch. Put the risky code in a normal method and call it from the coroutine.
See also
- Null & Nullable Types
- Debug (
LogException) - async & await (exceptions in async code)
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Microsoft C# documentation.