Skip to content

fix: lazily initialize tokenizer HTTP client#1117

Open
arnabnandy7 wants to merge 1 commit into
googleapis:mainfrom
arnabnandy7:fix/native-build
Open

fix: lazily initialize tokenizer HTTP client#1117
arnabnandy7 wants to merge 1 commit into
googleapis:mainfrom
arnabnandy7:fix/native-build

Conversation

@arnabnandy7

Copy link
Copy Markdown

Summary

Fixes #978.

Prevents LocalTokenizerLoader from eagerly creating an OkHttp client during class initialization. This allows GraalVM native-image builds, including Quarkus native builds, to analyze the class without attempting to capture build-time SSL and socket state.

Problem

LocalTokenizerLoader previously initialized its HTTP client as a static field:

private static OkHttpClient httpClient = new OkHttpClient();

During native-image generation, GraalVM could initialize LocalTokenizerLoader at build time and encounter the OkHttpClient object graph, including its SSL socket factory. Because this operating-system-dependent state cannot safely be stored in the native image heap, the build failed with an error tracing the object through:

okhttp3.OkHttpClient.sslSocketFactoryOrNull
com.google.genai.LocalTokenizerLoader.httpClient

Requiring consumers to configure LocalTokenizerLoader for runtime initialization is a workaround, but places framework-specific configuration on SDK users.

Changes

  • Replaced eager OkHttpClient construction with the initialization-on-demand holder pattern.
  • The default client is now created only when the tokenizer model must be downloaded.
  • Preserved the existing test override mechanism for injecting a mocked client.
  • Reset the injected client during test teardown to avoid state leaking between tests.
  • Added a regression test confirming that tokenizer lookup does not initialize the HTTP client.

Implementation details

The default client is stored in a nested holder class:

private static final class HttpClientHolder {
  private static final OkHttpClient INSTANCE = new OkHttpClient();

  private HttpClientHolder() {}
}

The JVM initializes this nested class only when HttpClientHolder.INSTANCE is first accessed. This provides lazy and thread-safe initialization without explicit synchronization.

Calls that do not require network access, such as resolving the tokenizer name or loading an already cached model, therefore do not construct the HTTP client.

Compatibility

This change does not affect the public API or normal tokenizer behavior.

Applications should no longer need the following Quarkus workaround for LocalTokenizerLoader:

--initialize-at-run-time=com.google.genai.LocalTokenizerLoader

The separate interactions-client linkage error originally reported against version 1.51.0 does not apply to current releases because the experimental interactions implementation was removed in the 1.54.0 release line.

Validation

  • Checkstyle completed with zero violations.
  • git diff --check passed.
  • Production sources compiled successfully on JDK 21.
  • Bytecode inspection confirmed that LocalTokenizerLoader.<clinit> no longer constructs an OkHttpClient; construction is isolated to HttpClientHolder.<clinit>.
  • Added unit coverage for non-network tokenizer lookup without HTTP client initialization.

Full repository test/package execution remains affected by unrelated baseline issues involving generated type compilation and existing Animal Sniffer failures.

Signed-off-by: Arnab Nandy <arnab_nandy7@yahoo.com>
@hemasekhar-p hemasekhar-p self-assigned this Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native build issue

2 participants