{"record":{"id":"fb2269f42fa24953","repo":"thingsboard/thingsboard","slug":"failed-to-parse-service-account-key-json","errorCode":null,"errorMessage":"Failed to parse service account key JSON","messagePattern":"Failed to parse service account key JSON","errorType":"validation","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java","lineNumber":123,"sourceCode":"                .topP(chatModelConfig.topP())\n                .topK(chatModelConfig.topK())\n                .frequencyPenalty(chatModelConfig.frequencyPenalty())\n                .presencePenalty(chatModelConfig.presencePenalty())\n                .maxOutputTokens(chatModelConfig.maxOutputTokens())\n                .timeout(toDuration(chatModelConfig.timeoutSeconds()))\n                .maxRetries(chatModelConfig.maxRetries())\n                .build();\n    }\n\n    @Override\n    public ChatModel configureChatModel(GoogleVertexAiGeminiChatModelConfig chatModelConfig) {\n        GoogleCredentials credentials;\n        try {\n            credentials = ServiceAccountCredentials\n                    .fromStream(new ByteArrayInputStream(chatModelConfig.providerConfig().serviceAccountKey().getBytes(StandardCharsets.UTF_8)))\n                    .createScoped(\"https://www.googleapis.com/auth/cloud-platform\");\n        } catch (IOException e) {\n            throw new RuntimeException(\"Failed to parse service account key JSON\", e);\n        }\n        return GoogleGenAiChatModel.builder()\n                .projectId(chatModelConfig.providerConfig().projectId())\n                .location(chatModelConfig.providerConfig().location())\n                .googleCredentials(credentials)\n                .modelName(chatModelConfig.modelId())\n                .temperature(chatModelConfig.temperature())\n                .topP(chatModelConfig.topP())\n                .topK(chatModelConfig.topK())\n                .frequencyPenalty(chatModelConfig.frequencyPenalty())\n                .presencePenalty(chatModelConfig.presencePenalty())\n                .maxOutputTokens(chatModelConfig.maxOutputTokens())\n                .timeout(toDuration(chatModelConfig.timeoutSeconds()))\n                .maxRetries(chatModelConfig.maxRetries())\n                .build();\n    }\n\n    @Override","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/thingsboard/thingsboard/blob/45c30e83fa57356840d5f25d894002d94222d524/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java#L105-L141","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-download the JSON key file from Google Cloud IAM (Service Accounts > Keys) and paste its full, exact contents into the provider config.","Validate the pasted value parses as JSON and contains the fields google-libraries expect (client_email, private_key, project_id) before saving.","Ensure no env-var placeholder or base64 wrapper is left around the value.","If using env interpolation, confirm the variable is actually set in the service environment."],"exampleFix":"// before\nserviceAccountKey: \"${GCP_SERVICE_ACCOUNT_KEY}\"  // variable unset -> literal placeholder parsed\n\n// after\n// export GCP_SERVICE_ACCOUNT_KEY=$(cat ./sa-key.json) so interpolation yields valid JSON","handlingStrategy":"validation","validationCode":"// Validate the key parses and has required fields BEFORE configuring the model\nObjectNode node;\ntry {\n    node = (ObjectNode) JacksonUtil.fromString(serviceAccountKey, JsonNode.class);\n} catch (Exception e) { throw new IllegalArgumentException(\"key is not valid JSON\"); }\nif (node.get(\"client_email\") == null || node.get(\"private_key\") == null || node.get(\"project_id\") == null) {\n    throw new IllegalArgumentException(\"service account key missing client_email/private_key/project_id\");\n}\n// only then call configureChatModel(config)","typeGuard":null,"tryCatchPattern":"try { model = configurer.configureChatModel(geminiConfig); }\ncatch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Failed to parse service account key JSON\")) {\n        // reject config at save time, prompt user to re-upload the JSON key file\n    } else throw e;\n}","preventionTips":["Validate service-account JSON (parse + required fields) in the settings UI before saving AI provider config.","Store the key as a file reference/secret, never a hand-typed string."],"tags":["thingsboard","ai","google-vertex-ai","gemini","credentials","json"],"backgroundTag":null,"analyzedSha":"45c30e83fa57356840d5f25d894002d94222d524","analyzedAt":"2026-08-14T11:45:36.599Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}