- What is this guide for?
- How to create Google Service Account
- Configuration
- How to use Google Services
- FAQ - Common errors
- References
This guideline shows how to configure and use Google APIs with Spring and Kotlin. Google Services used as samples are Sheets and IAM.
In order to go further make sure you have a Google Service Account with at least one generated Key.
Reference: https://cloud.google.com/iam/docs/creating-managing-service-account-keys
Google has a bunch of dependencies to all kind of services on Google Cloud Platform.
Reference: https://github.com/googleapis/google-api-java-client-services#supported-google-apis
⚠️ WARNEvery Google API Service needs to enabled on GCP (Google Cloud Platform).
For example Sheets API or Admin SDK API
Add the below dependencies to your pom.xml
<!-- Required -> used to configure Google Service Account Credentials -->
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-jetty</artifactId>
<version>${google-api-client.version}</version>
</dependency>
<!-- Required -> used to configure Google Service Account Credentials -->
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>${google-auth-library-oauth2-http.version}</version>
</dependency>
<!-- Required -> used to convert google json responses -->
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-jackson2</artifactId>
<version>${google-http-client-jackson2.version}</version>
</dependency>
<!-- Optional -> used to access Google Sheets services -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-sheets</artifactId>
<version>${google-api-services-sheets.version}</version>
</dependency>
<!-- Optional -> used to access Google IAM services -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-admin-directory</artifactId>
<version>directory_v1-rev118-1.25.0</version>
</dependency>
Create a Configuration class with the following structure.
⚠️ WARNRead all comments from code below !
import com.google.api.services.admin.directory.DirectoryScopes
import com.google.auth.http.HttpCredentialsAdapter
import com.google.auth.oauth2.ServiceAccountCredentials
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/*
* Handles Google API Configuration of all Google Services.
*/
@Configuration("GoogleConfig")
class GoogleConfig {
/*
* This Bean manages Google Credentials for every Google Service you will use on this component
*/
@Bean
fun getCredentials(): HttpCredentialsAdapter {
val googleCredentials = ServiceAccountCredentials.fromPkcs8(
/*
* Credentials values from generated credentials.json.
* Replace all values here with the values of json file.
* Reference: https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-list-console
* */
"client_id",
"client_email",
"private_key",
"private_key_id",
/*
* List of all Google Scopes the Service Account will need.
* The list is String format and all Google Dependencies has it's own SCOPES.
* Reference: https://developers.google.com/identity/protocols/oauth2/scopes
* */
emptyList(),
)
return HttpCredentialsAdapter(googleCredentials)
}
}
Each Google Service has it's own dependency and it needs to be configured individually.
To add a new Service like Sheets and IAM:
- create a Bean for each of then on GoogleConfig class like below.
- make sure you put your application name, is a free text field
- add scopes for each service at
getCredentials
Bean
ℹ️ INFO
All Google Services are listed at https://github.com/googleapis/google-api-java-client-services#supported-google-apis
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.admin.directory.Directory
import com.google.api.services.admin.directory.DirectoryScopes
import com.google.api.services.sheets.v4.Sheets
import com.google.auth.http.HttpCredentialsAdapter
import com.google.auth.oauth2.ServiceAccountCredentials
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/*
* Handles Google API Configuration of all Google Services.
*/
@Configuration("GoogleConfig")
class GoogleConfig {
/*
* This Bean manages Google Credentials for every Google Service you will use on this component
*/
@Bean
fun getCredentials(): HttpCredentialsAdapter {
val googleCredentials = ServiceAccountCredentials.fromPkcs8(
/*
* Credentials values from generated credentials.json.
* Replace all values here with the values of json file.
* Reference: https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-list-console
* */
"client_id",
"client_email",
"private_key",
"private_key_id",
/*
* List of all Google Scopes the Service Account will need.
* The list is String format and all Google Dependencies has it's own SCOPES.
* The first element of the example below is String format and the second is getting from Dependency SCOPE.
* Reference: https://developers.google.com/identity/protocols/oauth2/scopes
* */
listOf(
"https://www.googleapis.com/auth/spreadsheets.readonly", DirectoryScopes.ADMIN_DIRECTORY_GROUP
),
)
return HttpCredentialsAdapter(googleCredentials)
}
/*
* Configuration for Google Sheets service
*/
@Bean
fun getGoogleSheetsService(credentials: HttpCredentialsAdapter): Sheets {
return Sheets.Builder(
GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), credentials
).setApplicationName("your application name").build()
}
/*
* Configuration for Google IAM service
*/
@Bean
fun getDirectoryService(credentials: HttpCredentialsAdapter): Directory {
return Directory.Builder(
GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), credentials
).setApplicationName("your application name").build()
}
}
At your External Interfaces you can use all your configured Google Services as you wish.
No need to handle Google Service Account authentication.
Every Google Service has it's own methods, types and exceptions. So be sure to read the documentation of the service you want to use.
Below is a sample implementation to read a Google Sheet range.
import com.amaro.secondpartner.sample.usecases.ports.StoragePort
import com.google.api.services.sheets.v4.Sheets
import org.springframework.stereotype.Component
@Component
class SheetsStorage(private val sheets: Sheets) : StoragePort {
override fun getSheetRange(spreadsheetId: String): MutableList<MutableList<Any>>? {
/*
* Read Google Sheets API documentation to know how it works
*/
return sheets.spreadsheets().values()
.get(spreadsheetId, "A1:B2")
.setValueRenderOption("FORMATTED_VALUE")
.execute()
.getValues()
}
}
Below is a sample use case without implementation to list 10 IAM groups.
import com.google.api.services.admin.directory.Directory
import com.google.api.services.admin.directory.model.Group
import com.google.api.services.admin.directory.model.Groups
import com.google.api.services.admin.directory.model.User
import com.google.api.services.admin.directory.model.Users
import org.springframework.stereotype.Service
@Service
class ReadUsersUseCase(
val directory: Directory
) {
fun allFromGCP(): List<User> {
val result: Users = directory.users().list()
.setMaxResults(10)
.setOrderBy("email")
.execute()
return result.users
}
fun allUserGroupsFromGCP(): List<Group> {
val result: Groups = directory.groups().list()
.setMaxResults(10)
.execute()
return result.groups
}
}
If your code throws something like the below message.
Make sure to enable Google Service API you are trying to access on GCP.
✔️ Solution
Visit the error message link in order to enable the API in GCP.
You can also visit https://console.cloud.google.com/projectselector2/apis/dashboard?organizationId=0&supportedpurview=project to search for Google APIs.
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
GET https://www.googleapis.com/admin/directory/v1/groups?maxResults=10
{
"code" : 403,
"details" : [ {
"@type" : "type.googleapis.com/google.rpc.Help"
}, {
"@type" : "type.googleapis.com/google.rpc.ErrorInfo"
} ],
"errors" : [ {
"domain" : "usageLimits",
"message" : "Admin SDK API has not been used in project 323312394567 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/admin.googleapis.com/overview?project=323312394567 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
"reason" : "accessNotConfigured",
"extendedHelp" : "https://console.developers.google.com"
} ],
"message" : "Admin SDK API has not been used in project 323312394567 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/admin.googleapis.com/overview?project=323312394567 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
"status" : "PERMISSION_DENIED"
}
The error message java.io.IOException: Invalid PKCS#8 data.
is caused by wrong Google Service Account Private Key value.
✔️ Solution for Production and Dev Environment
In production environment the GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment variable is set by Kubernetes. Contact Devops team to update it for you.
Example of Private Key format with Spring configuration
Break lines must be replaced by
\n
src/main/resources/application.yml
# FAKE PRIVATE KEY VALUE google: service-account: private_key: "-----BEGIN PRIVATE KEY-----\nMIIEdsuhINidHidhddsaduhiDHiuhJDSOAIjoidjadASDhbdsbiuadBHUtpIbNBC\nIASjdosiadjaoiDsa98dsjvCZ5IZbVwYgI/ASdhudshaiURbmsOqBEk3jnrC1Lqe\nyEugQeDNf+OU+AO6dR1OLO7S5XFLhcTGPsaxBTPTi7bDypJFmb9mdAgh9zATegJP\nQC8UHrAGD5x6ahkzYctEtLtByBPbFHlzGyCMrpS3PBQHjaOju5qdDAyhVUMtKAAV\nyrz5JKdoozZp/MuQVXgSZnBmbUu+toXLU6ZE6CJ+Ke/+gjIIYE+A788HgQoUh5zf\nBoEo0epcV7HUlLYZXm/D99G6sp1Atd6jMyOAduKz9i1mC8JlhC0E1pwTxMryko0a\nEJqwIzIxAgMBAAECggEAAdSXBXeIdr+gEfRyfm7ZbyoMY+PLpGxRwgmrHiXFhpH2\nVxv3LkDORADqOanp17aUnSjdaCP3og/Gc+OScOoywXKkfzmUezSGNGG2F6AXoK78\nWKPi1JJQKPBSo/gFPlUzS68k5RxTRsOmwvb4Ucrk4EHnnIO5i098e8ksG8Ozy/u6\nx2rrdFyySlPigqxVFp0aCUBTD/dKLeJc0f1PYxxEseVlMwv4WPratO3VF7vSzbwZ\nqt0s+xQxfNEm2guOahpVLm2XMzkkSEQg5fY6ZGnmSDVclI8FLgmCpBMAiPC1q4hm\nGKDCcd9QsARYyYIcKS2Wi7f1F5LcSAIdC6GheIu+MQKBgQD64fjnNYA6jXOyvr0F\naBnujL8zCFtbfS1394S9jEla8a9N0W7L5bqz9FOiPVkZmO2OGVtXtbEjo7V5dHLs\no7jOy7g4rnagYhZyc74Ft96PNm/5f+R8+Hqo4YmDF0wq3bAWk7q3/vfy7bmQ1gtG\nX+6FjoKNBX+W+TOkmIRZxs3NrQKBgQDvO7qNwSyxCoIU9KwL/39TKaLqdpxwMHVC\nZZaHTazdpMVUnKOmIhTV529azWT61R0nk9yzKg/afb3X1mfD+rY0HGX4VojovhPb\nJ9e7VB6JSnC/isGy2H9fW7A2UWQj4ZIO7S/WF7axZqhHlGO26oadosW6PChBtyFX\nX1qrjyr/FQKBgQDB5rzVFgiROJpfYAP/lcHVGXr9GkxhnZHy0p8JUM+xNnJjFqcn\nd9qhEwlAr2GZ4xRXa0mptIfAH9s2j8XzF2bSjalIRa7xrl3i/4myKbYdwdkxYKNb\n40AKHqaFZxA7YTOjf3Ikmy9P1mRBiO0V6zcSil5kWAeQy/IaGLbSHFEb0QKBgEC3\nlrr8VBu8rP4ARn6kaoxTyifFWIKdCUuh8bu+jpITHfLKwRaTR7Gp/xzATZ3xgwaO\n4HhzW4CO3YmDDeUdcKbeO9OXjUfxC6wQtjKOCgi345JddhLssGBajGvVTtLKFdoF\n3hf7qEeLbuCWSvdTsID6ZKUB2x4T7WgWeo/IligVAoGBAOh9uLr9c3awcDiB36zs\nDpxJrkF5KDAhEdPNsa6ZqKhN8Q2YNe+mMV184Q58VtPmjuKA/XVhZ0aw8Wr1vRuk\nGVj4BwX0bP7hN0l7nGoBrEDCvRBTajo96T1KrfEYZyr0KHtJ40jJqHryXq/1mh53\nSYgZUc2+rpgkq3G2WV/yNvER\n-----END PRIVATE KEY-----"
✔️ Solution for environment variable on Docker
When running locally with Docker the GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment variable is set by
local.env
file.Example of Private Key format with Docker
Break lines must exist in file.
local.env
# FAKE PRIVATE KEY VALUE GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- MIIEdsuhINidHidhddsaduhiDHiuhJDSOAIjoidjadASDhbdsbiuadBHUtpIbNBC IASjdosiadjaoiDsa98dsjvCZ5IZbVwYgI/ASdhudshaiURbmsOqBEk3jnrC1Lqe yEugQeDNf+OU+AO6dR1OLO7S5XFLhcTGPsaxBTPTi7bDypJFmb9mdAgh9zATegJP QC8UHrAGD5x6ahkzYctEtLtByBPbFHlzGyCMrpS3PBQHjaOju5qdDAyhVUMtKAAV yrz5JKdoozZp/MuQVXgSZnBmbUu+toXLU6ZE6CJ+Ke/+gjIIYE+A788HgQoUh5zf BoEo0epcV7HUlLYZXm/D99G6sp1Atd6jMyOAduKz9i1mC8JlhC0E1pwTxMryko0a EJqwIzIxAgMBAAECggEAAdSXBXeIdr+gEfRyfm7ZbyoMY+PLpGxRwgmrHiXFhpH2 Vxv3LkDORADqOanp17aUnSjdaCP3og/Gc+OScOoywXKkfzmUezSGNGG2F6AXoK78 WKPi1JJQKPBSo/gFPlUzS68k5RxTRsOmwvb4Ucrk4EHnnIO5i098e8ksG8Ozy/u6 x2rrdFyySlPigqxVFp0aCUBTD/dKLeJc0f1PYxxEseVlMwv4WPratO3VF7vSzbwZ qt0s+xQxfNEm2guOahpVLm2XMzkkSEQg5fY6ZGnmSDVclI8FLgmCpBMAiPC1q4hm GKDCcd9QsARYyYIcKS2Wi7f1F5LcSAIdC6GheIu+MQKBgQD64fjnNYA6jXOyvr0F aBnujL8zCFtbfS1394S9jEla8a9N0W7L5bqz9FOiPVkZmO2OGVtXtbEjo7V5dHLs o7jOy7g4rnagYhZyc74Ft96PNm/5f+R8+Hqo4YmDF0wq3bAWk7q3/vfy7bmQ1gtG X+6FjoKNBX+W+TOkmIRZxs3NrQKBgQDvO7qNwSyxCoIU9KwL/39TKaLqdpxwMHVC ZZaHTazdpMVUnKOmIhTV529azWT61R0nk9yzKg/afb3X1mfD+rY0HGX4VojovhPb J9e7VB6JSnC/isGy2H9fW7A2UWQj4ZIO7S/WF7axZqhHlGO26oadosW6PChBtyFX X1qrjyr/FQKBgQDB5rzVFgiROJpfYAP/lcHVGXr9GkxhnZHy0p8JUM+xNnJjFqcn d9qhEwlAr2GZ4xRXa0mptIfAH9s2j8XzF2bSjalIRa7xrl3i/4myKbYdwdkxYKNb 40AKHqaFZxA7YTOjf3Ikmy9P1mRBiO0V6zcSil5kWAeQy/IaGLbSHFEb0QKBgEC3 lrr8VBu8rP4ARn6kaoxTyifFWIKdCUuh8bu+jpITHfLKwRaTR7Gp/xzATZ3xgwaO 4HhzW4CO3YmDDeUdcKbeO9OXjUfxC6wQtjKOCgi345JddhLssGBajGvVTtLKFdoF 3hf7qEeLbuCWSvdTsID6ZKUB2x4T7WgWeo/IligVAoGBAOh9uLr9c3awcDiB36zs DpxJrkF5KDAhEdPNsa6ZqKhN8Q2YNe+mMV184Q58VtPmjuKA/XVhZ0aw8Wr1vRuk GVj4BwX0bP7hN0l7nGoBrEDCvRBTajo96T1KrfEYZyr0KHtJ40jJqHryXq/1mh53 SYgZUc2+rpgkq3G2WV/yNvER -----END PRIVATE KEY-----"
https://developers.google.com/learn/pathways/springboot-google-cloud
https://cloud.google.com/docs/authentication/getting-started
https://cloud.google.com/docs/authentication/production
https://mvnrepository.com/artifact/com.google.apis/google-api-services-iam
https://cloud.google.com/iam/docs/write-policy-client-libraries
https://cloud.google.com/resource-manager/docs/libraries
https://github.com/googleapis/google-api-java-client-services#supported-google-apis
https://github.com/googleapis/google-api-java-client/
https://mvnrepository.com/artifact/com.google.apis/google-api-services-admin-directory