thingsboard/thingsboard · error · RuntimeException

Failed to parse service account key JSON

Error message

Failed to parse service account key JSON

What it means

Thrown by Langchain4jChatModelConfigurerImpl when configuring a Google Vertex AI Gemini chat model: ServiceAccountCredentials.fromStream cannot parse the configured providerConfig().serviceAccountKey() bytes as valid Google service-account JSON. The RuntimeException aborts chat-model creation, so any AI feature relying on that model fails to start.

Source

Thrown at application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java:123

                .topP(chatModelConfig.topP())
                .topK(chatModelConfig.topK())
                .frequencyPenalty(chatModelConfig.frequencyPenalty())
                .presencePenalty(chatModelConfig.presencePenalty())
                .maxOutputTokens(chatModelConfig.maxOutputTokens())
                .timeout(toDuration(chatModelConfig.timeoutSeconds()))
                .maxRetries(chatModelConfig.maxRetries())
                .build();
    }

    @Override
    public ChatModel configureChatModel(GoogleVertexAiGeminiChatModelConfig chatModelConfig) {
        GoogleCredentials credentials;
        try {
            credentials = ServiceAccountCredentials
                    .fromStream(new ByteArrayInputStream(chatModelConfig.providerConfig().serviceAccountKey().getBytes(StandardCharsets.UTF_8)))
                    .createScoped("https://www.googleapis.com/auth/cloud-platform");
        } catch (IOException e) {
            throw new RuntimeException("Failed to parse service account key JSON", e);
        }
        return GoogleGenAiChatModel.builder()
                .projectId(chatModelConfig.providerConfig().projectId())
                .location(chatModelConfig.providerConfig().location())
                .googleCredentials(credentials)
                .modelName(chatModelConfig.modelId())
                .temperature(chatModelConfig.temperature())
                .topP(chatModelConfig.topP())
                .topK(chatModelConfig.topK())
                .frequencyPenalty(chatModelConfig.frequencyPenalty())
                .presencePenalty(chatModelConfig.presencePenalty())
                .maxOutputTokens(chatModelConfig.maxOutputTokens())
                .timeout(toDuration(chatModelConfig.timeoutSeconds()))
                .maxRetries(chatModelConfig.maxRetries())
                .build();
    }

    @Override

View on GitHub (pinned to 45c30e83fa)

Solutions

  1. Re-download the JSON key file from Google Cloud IAM (Service Accounts > Keys) and paste its full, exact contents into the provider config.
  2. Validate the pasted value parses as JSON and contains the fields google-libraries expect (client_email, private_key, project_id) before saving.
  3. Ensure no env-var placeholder or base64 wrapper is left around the value.
  4. If using env interpolation, confirm the variable is actually set in the service environment.

Example fix

// before
serviceAccountKey: "${GCP_SERVICE_ACCOUNT_KEY}"  // variable unset -> literal placeholder parsed

// after
// export GCP_SERVICE_ACCOUNT_KEY=$(cat ./sa-key.json) so interpolation yields valid JSON
Defensive patterns

Strategy: validation

Validate before calling

// Validate the key parses and has required fields BEFORE configuring the model
ObjectNode node;
try {
    node = (ObjectNode) JacksonUtil.fromString(serviceAccountKey, JsonNode.class);
} catch (Exception e) { throw new IllegalArgumentException("key is not valid JSON"); }
if (node.get("client_email") == null || node.get("private_key") == null || node.get("project_id") == null) {
    throw new IllegalArgumentException("service account key missing client_email/private_key/project_id");
}
// only then call configureChatModel(config)

Try / catch

try { model = configurer.configureChatModel(geminiConfig); }
catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("Failed to parse service account key JSON")) {
        // reject config at save time, prompt user to re-upload the JSON key file
    } else throw e;
}

Prevention

When it happens

Trigger: Setting the service account key in the ThingsBoard AI configuration to malformed JSON; pasting a truncated key; passing a base64-encoded or PEM key instead of the raw Google Cloud service-account JSON file contents; an empty string.

Common situations: Copy-paste errors when moving the key from the Google Cloud console (missing closing brace, smart quotes, extra whitespace/newlines); downloading the wrong key format; environment-variable interpolation producing a placeholder like '${GCP_KEY}' that was never substituted.

Understand the failure class

Related errors


AI-assisted analysis of thingsboard/thingsboard@45c30e83fa (2026-08-14). Data as JSON: /api/errors/fb2269f42fa24953. Report an issue: GitHub.