Getting started with GCP Cloud Firestore with Spring Boot

In this article we are going to integrate Firestore, a worth exploring database solution offered by GCP (Google Cloud Platform) in a spring boot application using spring cloud gcp.

For those who don’t know about firestore, In a nutshell it is a realtime No SQL document database, And its the defacto database of Firebase. I’m not going to explore firestore or firebase in detail rather take you through how to develop spring based java application to store your data in firestore. This integration will come in handy when you need to support your firebase application with a spring backend, or similaraly in any spring project to leverage GCP infrastructure.

Create Spring Boot Project

First let’s create a spring boot project using spring initializr. Create your own configurations or start with mine. Make sure you add GCP support by adding dependency if you choose your own configurations. My configurations are for maven + java 14 with jar packaging. So you should have java 14 ready in your PC to follow along. After open the downloaded project add following dependency to the pom or the build.gradle.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter-firestore</artifactId>
</dependency>

Create GCP Project or Firebase Project

First you need to create GCP project or Firebase project. To create GCP project go to google cloud console, login with your google account or else create one there, you can also get their free credit if you want. Then create a new project. Next we need to add firestore to the project. For that, on the menu goto storage then firestore. When you navigated to firestore page, select native mode. Then select location to deploy firestore. Make sure to choose it wisely. After few seconds you will be navigated to firestore data browser. You can add data manually. But who does do that?

To access Firestore (GCP API) you need to create service account. Go to IAM & Admin from the menu and select service accounts. There will be a service account created for Firestore you can use that, or else create new one from the above menu. If you decided to use existing one, click edit in the drop down menu. When creating new one you will have few options to try out, other wise in edit mode you can change name or description. I invite you to hack around a bit, as we developers usually do. Look for create key button and click, pick JSON from the popup then it will download a json file, keep it securely. Advice, don’t commit it to your public repo mistakenly. If you choose firebase, its rather easy, go to firebase console create a project then go to database section and create Firestore database. On screen information will you guide to download service account key. Either way now with your service account key you are ready to access Firestore in your code.

Setup Spring Boot Project with GCP Service Account

spring:
  cloud:
    gcp:
      project-id: your-project-id
      firestore:
        credentials:
          location: classpath:service-account-file.json

You can put your service account file in resources directory and use classpath: to point to the file otherwise you could use file: to point file in the file system. Make sure to add project id other wise you could end up with NullPointerException comes out of no where, I got it and finally manage to identify that I’ve missed the project id in application.yml. After that you could test app to see if it is ready to communicate with Firestore by updating your application class with following source.

package com.aptkode.example.firestore;

import com.google.cloud.firestore.Firestore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class FirestoreApplication {

	private final static Logger logger = LoggerFactory.getLogger(FirestoreApplication.class);

	@Autowired
	private Firestore firestore;

	public static void main(String[] args) {
		SpringApplication.run(FirestoreApplication.class, args);
	}

	@Bean
	public CommandLineRunner commandLineRunner() {
		return args -> {
			logger.info("{} app initialized.", firestore.getOptions().getProjectId());
		};
	}

}

Firestore Data Model

Firestore is a Document oriented database, there is no tables or rows as in conventional SQL databases. In Firestore data is organized in to collections. Collections can have Documents, which is the unit of storage in Firestore. Document can contain key value pairs, nested objects (max deep level is 100) and sub collections. Collections and Documents are created implicitly in Firestore, in other words it creates if the collection or document when it is unavailable. You don’t have to create collections explicitly, when you enter first document to the collection it is created, if it doesn’t have any document left then there collection is no longer exists. To simplify collections document separation, you can use / in the document method in java API as in the previous code snippet.

Save First Document

Let’s create User class and save it in Firestore. Make sure that your data class has a default constructor otherwise you won’t be able to deserialize Firestore document to java bean.

package com.aptkode.example.firestore;

public class User {
    private String name;
    private int age;

    public User() {
        // required for firestore
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Following method creates or overwrites a user in users collection by its id (tom).

private void writeAndReadDocument() throws ExecutionException, InterruptedException {
	// Add document with id "tom" using a custom User class to users collection
	User tom = new User("Tom", 18);

	// set asynchronously create/update document reference users/tom
	// users is the collection and tom is the document id
	ApiFuture<WriteResult> apiFuture = this.firestore.document("users/tom").set(tom);

	// .get() blocks on response
	WriteResult writeResult = apiFuture.get();

	logger.info("Update time: {}", writeResult.getUpdateTime());
}

Call it in command line runner.

Read Document as Java Object

private User readDocument() throws ExecutionException, InterruptedException {
    // this.firestore.collection("users").document("tom") works also
    ApiFuture<DocumentSnapshot> apiFuture = this.firestore.document("users/tom").get();

    // .get() blocks on response
    DocumentSnapshot documentSnapshot = apiFuture.get();

    return documentSnapshot.toObject(User.class);
}

Update Document

private void updateDocument() throws ExecutionException, InterruptedException {
    ApiFuture<WriteResult> apiFuture = this.firestore.document("users/tom")
            .set(new User("tom", 19));
    WriteResult writeResult = apiFuture.get();
    logger.info("Update time: {}", writeResult.getUpdateTime());
}

Delete Document

private void deleteDocument() throws ExecutionException, InterruptedException {
    // document deletion does not delete its sub collections
    // see https://firebase.google.com/docs/firestore/manage-data/delete-data#collections
    ApiFuture<WriteResult> apiFuture = this.firestore.document("users/tom").delete();
    WriteResult writeResult = apiFuture.get();
    logger.info("Update time: {}", writeResult.getUpdateTime());
}

Wire it Together

Following is the example of executing all queries in command line runner block. But you may have different way of using these. We’ll explore them in future articles, for now let’s do it in following way.

@Bean
public CommandLineRunner commandLineRunner() {
    return args -> {
        logger.info("{} app initialized.", firestore.getOptions().getProjectId());
        writeAndReadDocument();
        User user = readDocument();
        logger.info("user name: {} age: {}", user.getName(), user.getAge());
        updateDocument();
        user = readDocument();
        logger.info("user name: {} age: {}", user.getName(), user.getAge());
        deleteDocument();
    };
}

Firestore vs Firebase Realtime Database

Firestore is relatively new, earlier there was Realtime Database in the Firebase. Still its there. So what’s the difference? Not in the technical terms, from my experience Firestore is far better solution than Realtime database. Data modeling is much easier in Firestore, then all the CRUD operations, querying and listening to the realtime updates are easy. You can visit this page and get clear idea of what product should you use for your mobile backed either in firebase or firebase backed by custom backend.

What if I hate Spring

Trust me, there are people who hates spring. Doesn’t matter the reason, they can go ahead with GCP official java SDK for firestore. Setting up that require separate article. So I’ll show you how to use firebase admin sdk. You need to have following dependency.

<dependency>
    <groupId>com.google.firebase</groupId>
    <artifactId>firebase-admin</artifactId>
    <version>6.12.2</version>
</dependency>

Then, as previously you need to have service account key file. By having setup similar to following you could get the access to Firestore instance. It exposes all the methods we used earlier. So you should be in same page. If you want to see how to setup firebase project for your next project, head over to one of our earlier video.

InputStream serviceAccount = Application.class.getResourceAsStream("/" + FIREBASE_ADMIN_SDK_CONFIG_FILE);

FirebaseOptions options = new FirebaseOptions.Builder()
        .setCredentials(GoogleCredentials.fromStream(serviceAccount))
        .build();

FirebaseApp.initializeApp(options);

Firestore firestore = FirestoreClient.getFirestore(FirebaseApp.getInstance());

Final thoughts

Here we just touched the surface of Firestore in spring ecosystem and plain java approach, we could do more. Let’s keep digging interesting topics such as real time updates, transactions, collection manipulation, queries and more in future articles. Video version of this article is coming up on our youtube channel, subscribe for future updates and watch our existing video series on IntelliJ Plugin Development, GraphQL, spring boot, unit testing and android + Firebase. Read second part of this article here and If you have any question please put it in comments, or join our social network pages. Happy Coding.

2 thoughts on “Getting started with GCP Cloud Firestore with Spring Boot”

  1. If We Use spring-cloud-gcp-starter-firestore with
    spring:
    cloud:
    gcp:
    project-id: your-project-id
    firestore:
    credentials:
    location: classpath:service-account-file.json
    For Providing Service Account .
    How Can We Access FireBaseAuth.getUser(“UserId”)
    Any specific dependency that will Autowire FireBaseAuth Just like Firestore.??

  2. Hi, nice article! Found an error though, the sprint-cloud-gcp-starter-data-firestore dependency belongs to groupId com.google.cloud not org.springframework.cloud.

Leave a Comment

Your email address will not be published. Required fields are marked *