Skip to content

fix: Default to GsonFactory and lazy-load default JsonFactory - #1257

Open
lahirumaramba wants to merge 1 commit into
mainfrom
lm-fix-1231
Open

lahirumaramba wants to merge 1 commit into
mainfrom
lm-fix-1231

Conversation

@lahirumaramba

Copy link
Copy Markdown
Member

Today, the SDK defaults to JacksonFactory for JSON parsing even though firebase-admin does not declare google-http-client-jackson2 as a direct dependency (it only enters the classpath transitively via google-cloud-storage). When an application excludes google-cloud-storage or google-http-client-jackson2, making an HTTP call triggers JVM bytecode verification of ApiClientUtils and fails with NoClassDefFoundError, even when the caller explicitly passes a custom JsonFactory via FirebaseOptions.

This change switches the default JsonFactory to GsonFactory (which firebase-admin, google-api-client, and google-auth-library already depend on directly) and moves default JsonFactory initialization into a lazy holder class so custom JsonFactory configurations never load the default implementation. The upstream GsonFactory leniency issues that prevented this migration in 2021 (googleapis/google-api-java-client#1779) are resolved in google-http-client-gson 2.x, where strict parsing (readLeniency = false) is the default.

Fixes #1231

Technical Details
  • Lazy holder for JVM verification (ApiClientUtils): Previously, ApiClientUtils.getDefaultJsonFactory() returned JacksonFactory.getDefaultInstance() directly as a JsonFactory, forcing the JVM bytecode verifier to eagerly load JacksonFactory whenever ApiClientUtils was loaded (for example, inside ApiClientUtils.newAuthorizedRequestFactory(app)) to verify subtype assignability. Wrapping GsonFactory.getDefaultInstance() in private static class JsonFactoryInstanceHolder (matching TransportInstanceHolder) defers loading until getDefaultJsonFactory() is called.
  • Compatibility: FirebaseOptions.getJsonFactory() exposes only the abstract JsonFactory type, and pom.xml dependencies are unchanged. Malformed JSON inputs continue to throw IOException (MalformedJsonException), untyped JSON numbers parse to BigDecimal, and floats serialize identically (0.2f -> "0.2"). Supplementary Unicode code points (such as emojis in Template.toJSON()) are serialized by GsonFactory as literal UTF-8 rather than UTF-16 surrogate pairs, both of which are valid RFC 8259 JSON.
  • Tests (ApiClientUtilsTest, FirebaseOptionsTest): Added PackageBlockingClassLoader tests verifying that FCM requests and Remote Config Template serialization succeed when google-http-client-jackson2 and com.fasterxml.jackson.* are absent from the classpath, and that passing a custom JsonFactory does not load the default JsonFactory implementation class.

Switch the default JsonFactory in ApiClientUtils from JacksonFactory to
GsonFactory and initialize it lazily via a static holder class.

Previously, ApiClientUtils referenced JacksonFactory directly even though
firebase-admin only received google-http-client-jackson2 transitively from
google-cloud-storage. Excluding Cloud Storage or Jackson2 caused a
ClassNotFoundException when the JVM verified ApiClientUtils, even when
callers explicitly passed a custom JsonFactory to FirebaseOptions. The
upstream Gson regressions from 2021 (strict parsing, IOException on
malformed JSON, and float serialization) are resolved in
google-http-client-gson 2.x, which firebase-admin already depends on.

Fixes #1231
@lahirumaramba
lahirumaramba requested a review from a team November 5, 2026 20:11

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the default JsonFactory implementation from JacksonFactory to GsonFactory in ApiClientUtils. It introduces a lazy-loaded JsonFactoryInstanceHolder to hold the singleton GsonFactory instance and adds Javadoc documentation to the utility methods. Additionally, the test suite is updated to assert that GsonFactory is used by default, and new integration tests are added using a custom class loader to verify that the SDK functions correctly even when Jackson2 is absent from the classpath. There are no review comments, and I have no additional feedback to provide.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

google-http-client-jackson2 cannot be excluded even when a JsonFactory is configured via FirebaseOptions

2 participants