Error Handling
- Last UpdatedJul 28, 2026
- 4 minute read
- Engineering
- Integration Service 4.1
- Integrators
This section covers the exception types in the SDK, recommended handling patterns, retry strategies, and best practices.
Custom Exception Types
The SDK defines six custom exceptions, all in AVEVA.IntegrationService.DataAPI.SDK.Exceptions:
HubConnectionInitializationException
Thrown when the SignalR hub connection cannot be established.
try
{
var pubSubClient = await SignalRHubConnectionFactory.CreatePubSubClient(
httpClientFactory, hubConnectionManager, authType, host);
}
catch (HubConnectionInitializationException ex)
{
Console.WriteLine($"Failed to connect to SignalR hub: {ex.Message}");
// Check: host URL, network connectivity, authentication
}
DataPostMessageNotReceivedException
Thrown when a published message is not acknowledged by the hub.
try
{
var result = await pubSubClient.Publish(message, host, topic);
}
catch (DataPostMessageNotReceivedException ex)
{
Console.WriteLine($"Message not delivered: {ex.Message}");
// Consider retrying or checking hub status
}
EmptyDataObjectRecievedException
Thrown when an API call succeeds but returns an empty result set unexpectedly.
try
{
var data = await client.GetTableData("DataSource", "Table", filter);
}
catch (EmptyDataObjectRecievedException ex)
{
Console.WriteLine($"No data returned: {ex.Message}");
// Verify datasource name, table name, and filter
}
MethodNotApplicableException
Thrown when a method is called on a client/datasource type that does not support it. Most commonly seen when using IDataApiClient (generic client) with datasource-specific methods.
try
{
var result = await client.PostTableData("DataSource", "Table", data, "");
}
catch (MethodNotApplicableException ex)
{
Console.WriteLine($"Operation not supported for this datasource type: {ex.Message}");
}
InternalPubSubClientException
Thrown for internal errors within the Pub/Sub client infrastructure.
try
{
await pubSubClient.Subscribe("Topic");
}
catch (InternalPubSubClientException ex)
{
Console.WriteLine($"Internal Pub/Sub error: {ex.Message}");
// Log and consider rebuilding the pub/sub client
}
SignaRConectionFailedException
Thrown when the SignalR connection drops or cannot be maintained.
try
{
await pubSubClient.Subscribe("Topic");
}
catch (SignaRConectionFailedException ex)
{
Console.WriteLine($"SignalR connection failed: {ex.Message}");
// Implement reconnection logic
}
Handling Patterns
Catch Specific Exception Types
Always prefer specific catches over a blanket Exception catch:
var client = DataApiClientFactory.CreateDataApiClient<EngineeringClient>(host);
try
{
var data = await client.GetDataSources("");
// process data
}
catch (HubConnectionInitializationException ex)
{
_logger.LogError(ex, "Hub connection failed – check connectivity");
}
catch (EmptyDataObjectRecievedException ex)
{
_logger.LogWarning(ex, "No data returned from API");
}
catch (TimeoutException ex)
{
_logger.LogWarning(ex, "Request timed out");
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error calling DataAPI");
throw; // Re-throw if you cannot handle it here
}
Conditional Retry on Transient Exceptions
public async Task<IEnumerable<DatasourceDto>> GetDataSourcesResilient(string host)
{
int maxRetries = 3;
var client = DataApiClientFactory.CreateDataApiClient<EngineeringClient>(host);
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await client.GetDataSources("");
}
catch (TimeoutException) when (attempt < maxRetries)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
catch (HubConnectionInitializationException) when (attempt < maxRetries)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
catch (Exception ex)
{
_logger.LogError(ex, "Unrecoverable error on attempt {Attempt}", attempt);
throw;
}
}
throw new InvalidOperationException($"Operation failed after {maxRetries} attempts");
}
Retry Strategies
Manual Exponential Backoff
public async Task<T> ExecuteWithRetry<T>(Func<Task<T>> operation, int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await operation();
}
catch (Exception ex) when (IsTransient(ex) && attempt < maxRetries)
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
_logger.LogWarning(ex, "Attempt {Attempt} failed, retrying in {Delay}s", attempt, delay.TotalSeconds);
await Task.Delay(delay);
}
}
throw new InvalidOperationException($"Operation failed after {maxRetries} attempts");
}
private static bool IsTransient(Exception ex)
=> ex is TimeoutException || ex is HubConnectionInitializationException;
Polly Retry + Circuit Breaker
using Polly;
var retryPolicy = Policy
.Handle<TimeoutException>()
.Or<HubConnectionInitializationException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
onRetry: (outcome, timespan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} after {timespan.TotalSeconds}s due to {outcome.Exception?.GetType().Name}");
}
);
var circuitBreaker = Policy
.Handle<Exception>()
.CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromMinutes(1));
var policy = Policy.WrapAsync(retryPolicy, circuitBreaker);
var data = await policy.ExecuteAsync(async () =>
await client.GetDataSources("")
);
Simple Manual Circuit Breaker
private int _errorCount = 0;
private DateTime _lastErrorTime = DateTime.MinValue;
private const int MaxErrors = 5;
private const int ResetMinutes = 5;
public async Task<T> ExecuteWithCircuitBreaker<T>(Func<Task<T>> operation)
{
if (_errorCount >= MaxErrors &&
DateTime.UtcNow < _lastErrorTime.AddMinutes(ResetMinutes))
{
throw new InvalidOperationException("Circuit breaker open – retry later.");
}
try
{
var result = await operation();
_errorCount = 0;
return result;
}
catch (Exception ex)
{
_errorCount++;
_lastErrorTime = DateTime.UtcNow;
_logger.LogError(ex, "Error count: {ErrorCount}", _errorCount);
throw;
}
}
Logging Errors
Structured Logging with Serilog
using Serilog;
try
{
var data = await client.GetTableData(datasource, table, filter);
}
catch (Exception ex)
{
Log.Error(ex,
"Failed to retrieve table data {DataSource}.{Table} with filter {Filter}",
datasource, table, filter);
}
Log with Context (ILogger)
_logger.LogError(ex,
"Operation={Operation} Host={Host} Attempt={Attempt} Elapsed={Elapsed}ms",
"GetDataSources", host, attempt, stopwatch.ElapsedMilliseconds);
Log Full Exception Chain
catch (Exception ex)
{
_logger.LogError(
"Type={Type} Message={Message} Stack={Stack}",
ex.GetType().Name, ex.Message, ex.StackTrace);
if (ex.InnerException != null)
_logger.LogError("InnerException={Inner}", ex.InnerException.Message);
}
HTTP Status Codes

Handling Status Codes
var response = await httpClient.GetAsync(url);
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.OK:
var content = await response.Content.ReadAsStringAsync();
break;
case System.Net.HttpStatusCode.Unauthorized:
_logger.LogError("Authentication failed");
throw new InvalidOperationException("Invalid credentials");
case System.Net.HttpStatusCode.Forbidden:
throw new UnauthorizedAccessException("Not authorised to access this resource");
case System.Net.HttpStatusCode.NotFound:
throw new InvalidOperationException("Resource not found");
case System.Net.HttpStatusCode.RequestTimeout:
case System.Net.HttpStatusCode.ServiceUnavailable:
throw new InvalidOperationException("Service unavailable – retry later");
default:
response.EnsureSuccessStatusCode();
break;
}
Timeout Handling
Recognise Timeout Errors
try
{
var data = await client.GetDataSources("");
}
catch (TimeoutException ex)
{
Console.WriteLine("Request timed out");
}
catch (TaskCanceledException ex)
{
Console.WriteLine("Request was cancelled (likely due to timeout)");
}
catch (OperationCanceledException ex)
{
Console.WriteLine("Operation was cancelled");
}
Increase Timeout
var client = DataApiClientFactory.CreateDataApiClient<EngineeringClient>(
host: host,
authType: AuthenticationType.NTLM,
waitingTimeInMinutesForLiveData: 300 // 5 hours
);
Custom Timeout with CancellationToken
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
try
{
await client.GetDataSources().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation timed out after 5 minutes");
}
Best Practices
Catch Specific Exceptions
// Good
catch (TimeoutException ex) { /* handle */ }
catch (HubConnectionInitializationException ex) { /* handle */ }
// Avoid
catch (Exception ex) { /* too broad */ }
Log Before Rethrowing
catch (Exception ex)
{
_logger.LogError(ex, "Operation failed");
throw; // Preserves the original stack trace
}
Add Context to Exceptions
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to fetch '{table}' from '{datasource}'", ex);
}
Use Async All the Way
// Good
var data = await client.GetDataSources("");
// Avoid – blocks thread and wraps exceptions in AggregateException
var data = client.GetDataSources("").Result;
Never Swallow Exceptions Silently
// BAD – hides errors
catch (Exception) { }
// GOOD – at minimum, log it
catch (Exception ex)
{
_logger.LogError(ex, "Operation failed silently");
}