Admin SDK errors are divided into two categories:
- Programming errors: These are programming and configuration errors in
the user application. They mostly occur due to the incorrect usage of the SDK
(such as passing
null
to a method that doesn’t acceptnull
values), and other configuration errors at the Firebase project or SDK level (missing credentials, incorrect project ID string, and so on). - API errors: These include various recoverable errors that occur within the SDK implementation, all the errors that originate in Firebase backend services, and other transient errors (such as timeouts) that may occur while making RPC calls.
The Admin SDK signals programming errors by throwing an error that is native to the platform in question.
- Java: Throws instances of
IllegalArgumentException
,NullPointerException
or similar built-in runtime error type. - Python: Raises instances of
ValueError
,TypeError
or other built-in error type. - Go: Returns a generic error.
- .NET: Throws instances of
ArgumentException
,ArgumentNullException
or similar built-in error type.
In most situations you should not explicitly handle programming errors. Instead, you should fix your code and configuration to avoid programming errors altogether. Consider the following Java snippet:
String uid = getUserInput();
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
If the getUserInput()
method returns null
or empty strings, the
FirebaseAuth.getUser()
API throws an IllegalArgumentException
. Instead of
explicitly handling it, you can mitigate the issue by ensuring the
getUserInput()
method never returns an invalid UID string. If that’s not
possible, implement the necessary argument checking in your own code as follows:
String uid = getUserInput();
if (Strings.isNullOrEmpty(uid)) {
log.warn("UID must not be null or empty");
return;
}
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
As a principle, never retry on programming errors. Allowing for fail-fast semantics on programming errors is often the best course of action because it exposes programming bugs and configuration errors during development, where they can be promptly fixed. Fail-fast in this context can mean letting the errors propagate to a global error handler in your application, or just logging them for audit purposes followed by the termination of the current execution flow (the application should not have to crash). In general, follow the error handling best practices of your programming language and the application framework. This alone is often sufficient to correctly deal with this class of errors.
Typically, the bulk of your error handling efforts will focus on handling API errors. Some of these errors are recoverable, such as errors that resulted from a temporarily unavailable service, and some are even anticipated during the normal program execution flow, such as detecting invalid or expired ID tokens. The rest of this guide outlines how the Admin SDK represents such API errors, and the various options that are available for handling them.
Structure of an API error
An API error consists of the following components:
- Error code
- Error message
- Service error code (Optional)
- HTTP response (Optional)
Every API error is guaranteed to contain an error code and an error message. Certain API errors also contain a service error code that is specific to the API that generated the error. For example some errors generated by the Firebase Auth API contain a service error code that is specific to Firebase Auth. If the error was the result of an HTTP error response from a backend service, the API error also contains the corresponding HTTP response. This can be used to inspect the exact headers and contents of the original response, which is useful for debugging, logging, or implementing more sophisticated error handling logic.
All Admin SDK implementations except Node.js provide APIs that enable accessing the above components of API errors.
Error types and APIs by language
Java
In Java all API errors extend the FirebaseException
class. You can access the
error code, error message and the optional HTTP response from this base class.
public class FirebaseException extends Exception {
@NonNull
public ErrorCode getErrorCode() {
// ...
}
@NonNull
public String getMessage() {
// ...
}
@Nullable
public IncomingHttpResponse getHttpResponse() {
// ...
}
}
APIs that expose service error codes provide API-specific subclasses of
FirebaseException
. For example all public methods in the FirebaseAuth
API
are declared to throw instances of FirebaseAuthException
. You can access the
service error code from this derived class.
public class FirebaseAuthException extends FirebaseException {
@Nullable
public AuthErrorCode getAuthErrorCode() {
// ...
}
}
Python
In Python all API errors extend the exceptions.FirebaseError
class. You can access the error code, the error message, and the optional HTTP
response from this base class.
class FirebaseError(Exception):
@property
def code(self):
# ...
@property
def message(self):
# ...
@property
def http_response(self):
# ...
Moreover, Python Admin SDK offers separate derived classes for each error code. We refer to them as platform error classes.
class InvalidArgumentError(FirebaseError):
# ...
class NotFoundError(FirebaseError):
# ...
You can either catch FirebaseError
in your code and check its code
, or
perform an isinstance()
check against a platform error class. Or you can write
code to directly catch specific platform error types. The latter approach is
likely to result in more readable error handling code.
APIs that expose service error codes provide API-specific subclasses of platform
error classes. For example, all public methods in the auth
module may throw
API-specific error types such as auth.UserNotFoundError
and
auth.ExpiredIdTokenError
.
class UserNotFoundError(exceptions.NotFoundError):
# …
class ExpiredIdTokenError(exceptions.InvalidArgumentError):
# ...
Go
The Go Admin SDK provides an errorutils
package which contains a series of
functions that allow testing for error codes.
package errorutils
func IsInvalidArgument(err error) bool {
// ...
}
func IsNotFound(err error) bool {
// ...
}
The error message is simply the string returned by the Error()
function of an
error. The optional HTTP response can be accessed by calling the
errorutils.HTTPResponse()
function, which returns an *http.Response
.
It is safe to pass nil
or any other error value to the error checking
functions in the errorutils
package. They return true
if the input argument
actually contains the error code in question, and return false
for everything
else. The HTTPResponse()
function has similar behavior, except it returns
nil
instead of false
.
APIs that expose service error codes provide API-specific error checking
functions in the corresponding packages. For example, the auth
package
provides the functions IsUserNotFound()
and IsExpiredIDTokenError()
.
.NET
In .NET all API errors extend the FirebaseException
class. You can access the
platform error code, error message and the optional HTTP response from this base
class.
public class FirebaseException : Exception {
public ErrorCode ErrorCode { get; }
public String Message { get; }
public HttpResponseMessage HttpResponse { get; }
}
APIs that expose service error codes provide API-specific subclasses of
FirebaseException
. For example, all public methods in the FirebaseAuth
API
are declared to throw instances of FirebaseAuthException
.
You can access the
service error code from this derived class.
public class FirebaseAuthException : FirebaseException {
public AuthErrorCode AuthErrorCode { get; }
}
Platform error codes
Error codes are common across all Firebase and Google Cloud Platform services. The following table outlines all the possible platform error codes. This is a stable list, and is expected to remain unchanged for a long period.
INVALID_ARGUMENT | Client specified an invalid argument. |
FAILED_PRECONDITION | Request cannot be executed in the current system state, such as deleting a non-empty directory. |
OUT_OF_RANGE | Client specified an invalid range. |
UNAUTHENTICATED | Request not authenticated due to missing, invalid or expired OAuth token. |
PERMISSION_DENIED | Client does not have sufficient permission. This can happen because the OAuth token does not have the right scopes, the client does not have permission, or the API has not been enabled for the client project. |
NOT_FOUND | Specified resource not found, or the request is rejected due to undisclosed reasons such as whitelisting. |
CONFLICT | Concurrency conflict, such as read-modify-write conflict. Only used by a few legacy services. Most services use ABORTED or ALREADY_EXISTS instead of this. Refer to the service-specific documentation to see which one to handle in your code. |
ABORTED | Concurrency conflict, such as read-modify-write conflict. |
ALREADY_EXISTS | The resource that a client tried to create already exists. |
RESOURCE_EXHAUSTED | Either out of resource quota or reaching rate limiting. |
CANCELLED | Request cancelled by the client. |
DATA_LOSS | Unrecoverable data loss or data corruption. The client should report the error to the user. |
UNKNOWN | Unknown server error. Typically a server bug.
This error code is also assigned to local response parsing (unmarshal) errors, and a wide range of other low-level I/O errors that are not easily diagnosable. |
INTERNAL | Internal server error. Typically a server bug. |
UNAVAILABLE | Service unavailable. Typically the server is temporarily down.
This error code is also assigned to local network errors (connection refused, no route to host). |
DEADLINE_EXCEEDED | Request deadline exceeded. This will happen only if the caller sets a deadline that is shorter than the target API’s default deadline (i.e. requested deadline is not enough for the server to process the request), and the request did not finish within the deadline.
This error code is also assigned to local connection and read timeouts. |
Most APIs can only result in a subset of the above error codes. In any case, you are not expected to explicitly handle all these error codes when implementing your error handlers. Most applications would only be interested in 1-2 specific error codes and treat everything else as a generic, unrecoverable failure.
Service-specific error codes
Firebase Auth
CERTIFICATE_FETCH_FAILED | Failed to fetch public key certificates required to verify a JWT (ID token or session cookie). |
EMAIL_ALREADY_EXISTS | A user already exists with the provided email. |
EXPIRED_ID_TOKEN | The ID token specified to verifyIdToken() is expired.
|
EXPIRED_SESSION_COOKIE | The session cookie specified to verifySessionCookie() iis expired.
|
INVALID_DYNAMIC_LINK_DOMAIN | The provided dynamic link domain is not configured or authorized for the current project. Related to email action link APIs. |
INVALID_ID_TOKEN | The ID token specified to verifyIdToken() is invalid.
|
INVALID_SESSION_COOKIE | The session cookie specified to verifySessionCookie() is invalid.
|
PHONE_NUMBER_ALREADY_EXISTS | A user already exists with the provided phone number. |
REVOKED_ID_TOKEN | The ID token specified to verifyIdToken() is revoked.
|
REVOKED_SESSION_COOKIE | The session cookie specified to verifySessionCookie() is expired.
|
UNAUTHORIZED_CONTINUE_URL | The domain of the continue URL is not whitelisted. Related to email action link APIs. |
USER_NOT_FOUND | No user record found for the given identifier. |
Firebase Cloud Messaging
THIRD_PARTY_AUTH_ERROR | APNs certificate or web push auth API key was invalid or missing. |
INVALID_ARGUMENT | One or more arguments specified in the request were invalid. |
INTERNAL | Internal server error. |
QUOTA_EXCEEDED | Sending limit exceeded for the message target. |
SENDER_ID_MISMATCH | The authenticated sender ID is different from the sender ID for the registration token. This usually means the sender and the target registration token are not in the same Firebase project. |
UNAVAILABLE | Cloud Messaging service is temporarily unavailable. |
UNREGISTERED | App instance was unregistered from FCM. This usually means that the device registration token used is no longer valid and a new one must be used. |
Automatic retries
The Admin SDK automatically retries certain errors before exposing those errors to the users. In general, following types of errors are transparently retried:
- All API errors resulting from HTTP 503 (Service Unavailable) responses.
- Some API errors resulting from HTTP 500 (Internal Server Error) responses.
- Most low-level I/O errors (connection refused, connection reset etc).
The SDK will retry each of the above errors up to 5 times (the original attempt + 4 retries) with exponential backoff. You can implement your own retry mechanisms at the application level if you want, but this is typically not required.
Retry-After support
The Go and .NET implementations of the Admin SDK come with support for
handling the HTTP Retry-After
header. That is, if the error response sent by
the backend servers contain the standard Retry-After
header, the SDK will
respect that when retrying as long as the specified wait duration is not very
long. If the Retry-After
header indicates a very long wait duration, the SDK
will bypass retries and throw the appropriate API error.
The Python Admin SDK does not currently support the Retry-After
header, and
only supports simple exponential backoff.
API error handling examples
Implementing a generic error handler
In most cases what you want is a generic error handler that catches a broad range of errors to prevent unexpected termination of the program flow due to an API error. Such error handlers usually just log the errors for audit purposes, or invoke some other default error handling routine for all encountered API errors. They are not necessarily interested in the different error codes or the reasons that may have caused the error.
Java
try {
FirebaseToken token = FirebaseAuth.getInstance().verifyIdToken(idToken);
performPrivilegedOperation(token.getUid());
} catch (FirebaseAuthException ex) {
System.err.println("Failed to verify ID token: " + ex.getMessage());
}
Python
try:
token = auth.verify_id_token(idToken)
perform_privileged_pperation(token.uid)
except exceptions.FirebaseError as ex:
print(f'Failed to verify ID token: {ex}')
Go
token, err := client.VerifyIDToken(ctx, idToken)
if err != nil {
log.Printf("Failed to verify ID token: %v", err)
return
}
performPrivilegedOperation(token)
.Net
try
{
var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex)
{
Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
}
Checking error codes
In some cases, you would want to inspect the exact error codes, and invoke different context-aware error handling routines. In the following example, we have an error handler that logs more specific error messages based on the service error code.
Java
try {
FirebaseToken token = FirebaseAuth.getInstance().verifyIdToken(idToken);
performPrivilegedOperation(token.getUid());
} catch (FirebaseAuthException ex) {
if (ex.getAuthErrorCode() == AuthErrorCode.ID_TOKEN_EXPIRED) {
System.err.println("ID token has expired");
} else if (ex.getAuthErrorCode() == AuthErrorCode.ID_TOKEN_INVALID) {
System.err.println("ID token is malformed or invalid");
} else {
System.err.println("Failed to verify ID token: " + ex.getMessage());
}
}
Python
try:
token = auth.verify_id_token(idToken)
perform_privileged_operation(token.uid)
except auth.ExpiredIdTokenError:
print('ID token has expired')
except auth.InvalidIdTokenError:
print('ID token is malformed or invalid')
except exceptions.FirebaseError as ex:
print(f'Failed to verify ID token: {ex}')
Go
token, err := client.VerifyIDToken(ctx, idToken)
if auth.IsIDTokenExpired(err) {
log.Print("ID token has expired")
return
}
if auth.IsIDTokenInvalid(err) {
log.Print("ID token is malformed or invalid")
return
}
if err != nil {
log.Printf("Failed to verify ID token: %v", err)
return
}
performPrivilegedOperation(token)
.Net
try
{
var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex)
{
if (ex.AuthErrorCode == AuthErrorCode.ExpiredIdToken)
{
Console.WriteLine("ID token has expired");
}
else if (ex.AuthErrorCode == AuthErrorCode.InvalidIdToken)
{
Console.WriteLine("ID token is malformed or invalid");
}
else
{
Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
}
}
Here’s another example where we check for both top-level and service error codes:
Java
try {
FirebaseMessaging.getInstance().send(createMyMessage());
} catch (FirebaseMessagingException ex){
if (ex.getMessagingErrorCode() == MessagingErrorCode.UNREGISTERED) {
System.err.println("App instance has been unregistered");
removeTokenFromDatabase();
} else if (ex.getErrorCode() == ErrorCode.Unavailable) {
System.err.println("FCM service is temporarily unavailable");
scheduleForRetryInAnHour();
} else {
System.err.println("Failed to send notification: " + ex.getMessage());
}
}
Python
try:
messaging.send(create_my_message())
except messaging.UnregisteredError:
print('App instance has been unregistered')
remove_token_from_database()
except exceptions.UnavailableError:
print('FCM service is temporarily unavailable')
schedule_for_retry_in_an_hour()
except exceptions.FirebaseError as ex:
print(f'Failed to send notification: {ex}')
Go
_, err := client.Send(ctx, createMyMessage())
if messaging.IsUnregistered(err) {
log.Print("App instance has been unregistered")
removeTokenFromDatabase()
return
}
if errorutils.IsUnavailable(err) {
log.Print("FCM service is temporarily unavailable")
scheduleForRetryInAnHour()
return
}
if err != nil {
log.Printf("Failed to send notification: %v", err)
return
}
.Net
try
{
await FirebaseMessaging.DefaultInstance.SendAsync(createMyMessage());
}
catch (FirebaseMessagingException ex)
{
if (ex.MessagingErrorCode == MessagingErrorCode.UNREGISTERED)
{
Console.WriteLine("App instance has been unregistered");
removeTokenFromDatabase();
}
else if (ex.ErrorCode == ErrorCode.Unavailable)
{
Console.WriteLine("FCM service is temporarily unavailable");
scheduleForRetryInAnHour();
}
else
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
}
Accessing the HTTP response
In some rare cases you may want to inspect the HTTP error response returned by a backend service and perform some error handling action on it. The Admin SDK exposes both the headers and the contents of these error responses. The response content is usually returned as a string or a raw byte sequence, and can be parsed into any target format necessary.
Java
try {
FirebaseMessaging.getInstance().send(createMyMessage());
} catch (FirebaseMessagingException ex){
IncomingHttpResponse response = ex.getHttpResponse();
if (response != null) {
System.err.println("FCM service responded with HTTP " + response.getStatusCode());
Map<String, Object> headers = response.getHeaders();
for (Map.Entry<String, Object> entry : headers.entrySet()) {
System.err.println(">>> " + entry.getKey() + ": " + entry.getValue());
}
System.err.println(">>>");
System.err.println(">>> " + response.getContent());
}
}
Python
try:
messaging.send(create_my_message())
except exceptions.FirebaseError as ex:
response = ex.http_response
if response is not None:
print(f'FCM service responded with HTTP {response.status_code}')
for key, value in response.headers.items():
print(f'>>> {key}: {value}')
print('>>>')
print(f'>>> {response.content}')
Go
_, err := client.Send(ctx, createMyMessage())
if resp := errorutils.HTTPResponse(err); resp != nil {
log.Printf("FCM service responded with HTTP %d", resp.StatusCode)
for key, value := range resp.Header {
log.Printf(">>> %s: %v", key, value)
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
log.Print(">>>")
log.Printf(">>> %s", string(b))
return
}
.Net
try
{
await FirebaseMessaging.DefaultInstance.SendAsync(createMyMessage());
}
catch (FirebaseMessagingException ex)
{
var response = ex.HttpResponse
if response != null
{
Console.WriteLine($"FCM service responded with HTTP { response.StatusCode}");
var headers = response.Headers;
for (var entry in response.Headers)
{
Console.WriteLine($">>> {entry.Key}: {entry.Value}");
}
var body = await response.Content.ReadAsString();
Console.WriteLine(">>>");
Console.WriteLine($">>> {body}");
}
}