Skip to main content
Robust error handling is critical for reliable integrations. Home Assistant provides a comprehensive exception hierarchy and patterns for handling errors gracefully.

Exception Hierarchy

Home Assistant defines specialized exceptions in homeassistant/exceptions.py:

Base Exception

from homeassistant.exceptions import HomeAssistantError

class HomeAssistantError(Exception):
    """General Home Assistant exception occurred."""
All Home Assistant exceptions inherit from HomeAssistantError.

Integration Exceptions

ConfigEntryNotReady

Raise when setup fails due to temporary conditions:
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up from a config entry."""
    try:
        client = await create_client(entry.data["host"])
        await client.connect()
    except ConnectionError as err:
        # Temporary failure - Home Assistant will retry
        raise ConfigEntryNotReady(
            f"Unable to connect to {entry.data['host']}"
        ) from err
    
    # Setup successful
    return True
When to use: Network issues, device offline, service unavailable Behavior: Home Assistant retries setup automatically with exponential backoff

ConfigEntryAuthFailed

Raise when authentication fails:
from homeassistant.exceptions import ConfigEntryAuthFailed

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up from a config entry."""
    try:
        client = await authenticate(
            entry.data["username"],
            entry.data["password"]
        )
    except AuthenticationError as err:
        # Invalid credentials - user must reauthenticate
        raise ConfigEntryAuthFailed(
            "Invalid credentials, please reauthenticate"
        ) from err
    
    return True
When to use: Invalid credentials, expired tokens, unauthorized access Behavior: Triggers reauthentication flow for user to fix credentials

ConfigEntryError

Raise for permanent configuration errors:
from homeassistant.exceptions import ConfigEntryError

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up from a config entry."""
    if not entry.data.get("api_key"):
        # Permanent configuration error
        raise ConfigEntryError(
            "API key is required but not configured"
        )
    
    try:
        client = APIClient(entry.data["api_key"])
        info = await client.get_account_info()
    except InvalidAPIKeyError as err:
        # Invalid API key - can't be fixed by retrying
        raise ConfigEntryError(
            "API key is invalid or has been revoked"
        ) from err
    
    return True
When to use: Invalid configuration, incompatible device, permanent failures Behavior: Config entry fails permanently, user must reconfigure

Service Exceptions

ServiceValidationError

Raise when service call parameters are invalid:
from homeassistant.exceptions import ServiceValidationError

async def async_set_temperature(self, temperature: float) -> None:
    """Set target temperature."""
    if not self.min_temp <= temperature <= self.max_temp:
        raise ServiceValidationError(
            f"Temperature {temperature} is outside valid range "
            f"{self.min_temp}-{self.max_temp}",
            translation_domain="my_integration",
            translation_key="invalid_temperature",
            translation_placeholders={
                "temperature": str(temperature),
                "min_temp": str(self.min_temp),
                "max_temp": str(self.max_temp),
            },
        )
    
    await self.device.set_temperature(temperature)

Translation Support

Exceptions can include translations for better user experience:
from homeassistant.exceptions import HomeAssistantError

raise HomeAssistantError(
    translation_domain="my_integration",
    translation_key="connection_failed",
    translation_placeholders={
        "device_name": device.name,
        "error": str(err),
    },
)
Corresponding strings.json:
{
  "exceptions": {
    "connection_failed": {
      "message": "Failed to connect to {device_name}: {error}"
    }
  }
}

Error Handling Patterns

Setup Error Handling

Handle errors during integration setup:
from homeassistant.exceptions import ConfigEntryNotReady, ConfigEntryAuthFailed
import asyncio

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up from a config entry."""
    try:
        # Test connection with timeout
        async with asyncio.timeout(10):
            client = await create_client(entry.data)
            await client.connect()
            
    except asyncio.TimeoutError as err:
        # Timeout - temporary issue
        raise ConfigEntryNotReady(
            "Timeout connecting to device"
        ) from err
        
    except AuthenticationError as err:
        # Authentication failed - need user action
        raise ConfigEntryAuthFailed(
            "Authentication failed, please reauthenticate"
        ) from err
        
    except ConnectionRefusedError as err:
        # Device not ready - temporary issue
        raise ConfigEntryNotReady(
            "Device refused connection, may be starting up"
        ) from err
        
    except InvalidConfigError as err:
        # Configuration is wrong - permanent error
        raise ConfigEntryError(
            "Invalid configuration"
        ) from err
    
    # Store client for later use
    entry.runtime_data = client
    
    # Forward setup to platforms
    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
    
    return True

Coordinator Error Handling

Handle errors in data update coordinators:
from homeassistant.helpers.update_coordinator import (
    DataUpdateCoordinator,
    UpdateFailed,
)
from homeassistant.exceptions import ConfigEntryAuthFailed
import logging

_LOGGER = logging.getLogger(__name__)

class MyCoordinator(DataUpdateCoordinator):
    """Coordinator for fetching data."""
    
    async def _async_update_data(self):
        """Fetch data from API."""
        try:
            return await self.client.fetch_data()
            
        except AuthenticationError as err:
            # Authentication failed - trigger reauthentication
            raise ConfigEntryAuthFailed(
                "Token expired, please reauthenticate"
            ) from err
            
        except ConnectionError as err:
            # Temporary network issue
            raise UpdateFailed(
                f"Failed to fetch data: {err}"
            ) from err
            
        except APIError as err:
            # Log the error but don't fail completely
            _LOGGER.warning("API error: %s", err)
            # Return previous data if available
            return self.data

Entity Error Handling

Handle errors in entity methods:
from homeassistant.exceptions import ServiceValidationError, HomeAssistantError
from homeassistant.helpers.entity import Entity
import logging

_LOGGER = logging.getLogger(__name__)

class MyEntity(Entity):
    """Example entity with error handling."""
    
    async def async_turn_on(self, **kwargs):
        """Turn on the device."""
        try:
            await self.device.turn_on()
            
        except DeviceOfflineError as err:
            # Device is offline - mark unavailable
            self._attr_available = False
            raise HomeAssistantError(
                f"Device {self.name} is offline"
            ) from err
            
        except InvalidCommandError as err:
            # Invalid command - user error
            raise ServiceValidationError(
                f"Invalid command: {err}",
                translation_domain=DOMAIN,
                translation_key="invalid_command",
            ) from err
            
        except Exception as err:
            # Unexpected error - log and raise
            _LOGGER.exception("Unexpected error turning on %s", self.name)
            raise HomeAssistantError(
                f"Failed to turn on {self.name}"
            ) from err
        
        # Success - update state
        self._attr_is_on = True
        self.async_write_ha_state()

API Client Error Handling

Handle errors in API clients:
from aiohttp import ClientError, ClientResponseError
import asyncio
import logging

_LOGGER = logging.getLogger(__name__)

class MyAPIClient:
    """API client with error handling."""
    
    async def fetch_data(self, endpoint: str) -> dict:
        """Fetch data from API endpoint."""
        try:
            async with asyncio.timeout(30):
                response = await self.session.get(f"{self.base_url}/{endpoint}")
                response.raise_for_status()
                return await response.json()
                
        except asyncio.TimeoutError as err:
            _LOGGER.warning("Timeout fetching %s", endpoint)
            raise ConnectionError("Request timed out") from err
            
        except ClientResponseError as err:
            if err.status == 401:
                raise AuthenticationError("Unauthorized") from err
            elif err.status == 404:
                raise ValueError(f"Endpoint {endpoint} not found") from err
            elif err.status >= 500:
                raise ConnectionError(f"Server error: {err.status}") from err
            else:
                raise ValueError(f"HTTP error {err.status}") from err
                
        except ClientError as err:
            _LOGGER.warning("Client error: %s", err)
            raise ConnectionError("Network error") from err

Logging Best Practices

Use appropriate log levels:
import logging

_LOGGER = logging.getLogger(__name__)

# DEBUG: Detailed diagnostic information
_LOGGER.debug("Fetching data from %s", url)

# INFO: General informational messages
_LOGGER.info("Successfully connected to device %s", device_name)

# WARNING: Something unexpected but recoverable
_LOGGER.warning("Device %s is offline, will retry", device_name)

# ERROR: Error occurred but integration continues
_LOGGER.error("Failed to update sensor %s: %s", sensor_name, err)

# EXCEPTION: Error with full traceback
try:
    await some_operation()
except Exception:
    _LOGGER.exception("Unexpected error in some_operation")

# Don't log sensitive information
_LOGGER.debug("Authenticated with token: %s", token[:8] + "...")

Testing Error Handling

Test error scenarios:
import pytest
from homeassistant.exceptions import ConfigEntryNotReady, ConfigEntryAuthFailed

async def test_setup_connection_error(hass, config_entry):
    """Test setup with connection error."""
    with patch(
        "custom_components.my_integration.create_client",
        side_effect=ConnectionError("Network error"),
    ):
        with pytest.raises(ConfigEntryNotReady):
            await async_setup_entry(hass, config_entry)

async def test_setup_auth_error(hass, config_entry):
    """Test setup with authentication error."""
    with patch(
        "custom_components.my_integration.create_client",
        side_effect=AuthenticationError("Invalid token"),
    ):
        with pytest.raises(ConfigEntryAuthFailed):
            await async_setup_entry(hass, config_entry)

Common Patterns

Retry Logic

import asyncio

async def async_retry_operation(operation, max_attempts=3):
    """Retry operation with exponential backoff."""
    for attempt in range(max_attempts):
        try:
            return await operation()
        except TemporaryError as err:
            if attempt == max_attempts - 1:
                raise
            delay = 2 ** attempt
            _LOGGER.debug("Retry %s/%s after %ss", attempt + 1, max_attempts, delay)
            await asyncio.sleep(delay)

Graceful Degradation

async def async_update(self):
    """Update entity state."""
    try:
        # Try to get full data
        data = await self.coordinator.client.get_full_data()
        self._attr_extra_state_attributes = data.attributes
    except FeatureNotSupportedError:
        # Fall back to basic data
        _LOGGER.debug("Full data not supported, using basic data")
        data = await self.coordinator.client.get_basic_data()
    
    self._attr_state = data.state
    self.async_write_ha_state()

Context Managers

from contextlib import asynccontextmanager

@asynccontextmanager
async def handle_device_errors(device_name: str):
    """Context manager for device operations."""
    try:
        yield
    except DeviceOfflineError as err:
        _LOGGER.warning("Device %s is offline", device_name)
        raise HomeAssistantError(f"{device_name} is offline") from err
    except Exception as err:
        _LOGGER.exception("Error with device %s", device_name)
        raise HomeAssistantError(f"Error with {device_name}") from err

# Usage
async with handle_device_errors(self.name):
    await self.device.some_operation()

Resources

Build docs developers (and LLMs) love