File operations in Google Drive API with Spring Boot

You are currently viewing File operations in Google Drive API with Spring Boot
Google Drive API integration with Spring boot

Spring boot application can be integrated to Google Drive APIs for all type of file operations using Google Drive Java client library. Google Drive API allows us to manage files and folders in Google Drive programmatically. In his article we will perform Google Drive integration in spring boot application, in which we will integrate all possible operations for listing, searching, upload, download etc.

Google Drive API supports direct HTTP requests and client library requests, all Google Drive file operations search, find, upload, download, create folder, list folder are accessible from both these methods. Google Drive API supports queries and special mime types to perform files and folders operations.

Google Drive Java client

The Java Client Library for Google API provides functionality common to all Google APIs like error handling, HTTP transport, JSON parsing, authentication and media download/upload. The Java Client library includes a powerful OAuth 2.0 library with a lightweight, consistent interface, efficient JSON and XML data models that support every type of data schema and protocol buffers.

To use Google APIs in Java, you would use the generated Java library for the Google APIs. This generated client libraries include the API-specific information such as root URL along with the core google-API-java-client. These libraries also include classes that represent entities for the API context, which are useful for making conversions between Java objects and JSON objects.

Google Drive API Gradle dependency 

Google periodically publishes its dependencies for different programming languages. For Gradle project, you can use following the Gradle dependencies :

compile 'com.google.API-client:google-API-client:1.23.0'
compile 'com.google.oauth-client:google-oauth-client-jetty:1.23.0'
compile 'com.google.APIs:google-API-services-drive:v3-rev110-1.23.0'

Google drive v3 Java client integration with Spring boot

Following are the steps to integrate Google Drive v3 API’s Java client with Spring boot application.

1. Enable Google Drive APIs

You need a Google account, on which you can enable Google Drive APIs. Visit Google’s Java QuickStart page, in case you have multiple Google login then switch to the Google account on which you want to enable Google Drive APIs. Click on the button “Enable the Drive API”, a Popup window will open as shown below.

Enable Google Drive API
Enable Google Drive API

2. Download Google drive project credentials

The Google drive popup will show a suggested name as QuickStart, you can use this name or change it and click the next button. In the next window, a drop-down will be shown, select the “Desktop app” option for the Java client library. Next window will show the credentials, download the file from here, this file will have default name as “credentials.json”.

Download crendentials for Google Drive API
Download crendentials for Google Drive API

3. Java Gradle Project setup

Spring boot Gradle project is recommended for this integration, as it will remove lots of boilerplate code. Visit the page for spring boot initializer and create a Gradle project with Java 11.

4. Add Google drive credentials and dependencies in Spring boot project

Open the Spring boot project in any IDE (Intellij Idea is recommended) and import the project as Gradle project. Once project import is complete, under resources directory, add your file “credentials.json”. In the build.gradle file, add all 3 Gradle dependencies for Google Drive client libraries and click the import button.

5. Configure Google drive API scopes 

Google Drive has different kinds of permissions which are divided into a few categories, these categories are called scopes. You can combine 1 or more scopes together as well. Since we need to perform every type of operation in Google Drive, so in our example we will use scope “DRIVE”.

Google drive API scopesPurpose
DRIVEView and manage the files in your Google Drive.
DRIVE_APPDATAView and manage its own configuration data in your Google Drive.
DRIVE_FILEView and manage Google Drive files and folders that you have opened or created with this app.
DRIVE_METADATAView and manage metadata of files in your Google Drive.
DRIVE_METADATA_READONLYView metadata for files in your Google Drive.
DRIVE_PHOTOS_READONLYView the photos, videos and albums in your Google Photos.
DRIVE_READONLYView the files in your Google Drive.
DRIVE_SCRIPTSModify your Google Apps Script’S behavior.

6. Allow Google Drive Api access for first time

Using credentials.json file, Google client library creates a URL with callback URL and prints into the console logs of the project. You need to open this URL in the browser and allow the access at least one time. By default, client library start a jetty server at 8080 port which will listen to the callback when you open the URL in the browser. If you want to change the port or host in callback URL then you can change it like following:

LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost("127.0.0.1").setPort(8089).build();

7. Access tokens saved automatically in spring boot project

Once you grant access to Google Drive API, then Google Client library automatically listens to it and saves the access tokens into your project root directory under a folder “tokens”. Next time if you run the project, it will not ask you to perform this step again, as access tokens will be already present there. If you delete this access token file, then on server startup you will be asked to perform the previous step again.

8. Google Drive API client instance creation

Next step is to combine everything and create a Google Drive API client instance, which will be used to perform every file/folder operation. Drive class is the service class which supports all operations on Google Drive. 

In the following example, we have created a GoogleDriveManager class, which has one method getInstance() which returns the instance of Drive. We will use this class in all examples to get the drive connection.

@Service
public class GoogleDriveManager {

  private static final String APPLICATION_NAME = "Technicalsand.com - Google Drive API Java Quickstart";
  private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
  private static final String TOKENS_DIRECTORY_PATH = "tokens";
  private static final List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE);
  private static final String CREDENTIALS_FILE_PATH = "/credentials.json";

  public Drive getInstance() throws GeneralSecurityException, IOException {
     // Build a new authorized API client service.
     final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
     Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
           .setApplicationName(APPLICATION_NAME)
           .build();
     return service;

  }

  /**
   * Creates an authorized Credential object.
   *
   * @param HTTP_TRANSPORT The network HTTP Transport.
   * @return An authorized Credential object.
   * @throws IOException If the credentials.json file cannot be found.
   */
  private Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
     // Load client secrets.
     InputStream in = GoogleDriveManager.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
     if (in == null) {
        throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
     }
     GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

     // Build flow and trigger user authorization request.
     GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
           HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
           .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
           .setAccessType("offline")
           .build();
     LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost("127.0.0.1").setPort(8089).build();

     return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
  }

}

Google Drive list files using Java client

Google Drive identifies every object (file or folder) using its unique id. If we don’t specify any parentId then Google Drive performs the operation on the entire Google Drive and list of every file/folder irrespective of its parent folder. Listing of files supports pagination from where you can control the number of results in a single API hit.

In the following example, we want to list the content of the whole Google Drive using pagination size 10. You will see it returns the results irrespective of Google Drive parent folder.

// FileManager.java
public List<File> listEverything() throws IOException, GeneralSecurityException {
  // Print the names and IDs for up to 10 files.
  FileList result = googleDriveManager.getInstance().files().list()
        .setPageSize(10)
        .setFields("nextPageToken, files(id, name)")
        .execute();
  return result.getFiles();
}

// MainController.java
@GetMapping({"/"})
public ResponseEntity<List<File>> listEverything() throws IOException, GeneralSecurityException {
  List<File> files = fileManager.listEverything();
  return ResponseEntity.ok(files);
}

Google Drive list files in folder using Java client

Google Drive API allows you to search for a specific set of folders or files under a folder. It supports query String “q” at file list level to filter the results. 

The format of a query string for Google Drive is: query_term operator values

  • First Example: ‘folderId’ in parents
  • Second Example: ‘root’ in parents
  • Third Example: mimeType=’image/jpeg’

First example query will print all the files and folders present under the folder which has this id. If you want to print files/folders present at the top level of Google drive then instead of folderId, mention it as root. If you specify the mimeType in the query then only the matching files will be shown in results.

In the following example code, we want the endpoint “/list” to print all the files/folders present at the root level of Google Drive (not at nested level). Output of this api will list every file/folder name with its id. For any folder, use its id in the api endpoint as  “/list/folderid/ then you will see the content of that particular folder.

// FileManager.java
public List<File> listFolderContent(String parentId) throws IOException, GeneralSecurityException {
  if(parentId == null){
     parentId = "root";
  }
  String query = "'" + parentId + "' in parents";
  FileList result = googleDriveManager.getInstance().files().list()
        .setQ(query)
        .setPageSize(10)
        .setFields("nextPageToken, files(id, name)")
        .execute();
  return result.getFiles();
}

// MainController.java
@GetMapping({"/list","/list/{parentId}"})
public ResponseEntity<List<File>> list(@PathVariable(required = false) String parentId) throws IOException, GeneralSecurityException {
  List<File> files = fileManager.listFolderContent(parentId);
  return ResponseEntity.ok(files);
}

Google Drive check if folder exists using Java client

To search for a folder by name in Google Drive, you need to use mimeType “application/vnd.google-apps.folder” in the query. To search folder at any level in Google Drive, in query part use root as “’root’ in parents”, if you want to search a folder under specific folder then query should be “‘parentId’ in parents”, where parentId is id of parent folder under which folder needs to be searched. Use a loop over pagination for results until all results are processed and compared with name.

In the following example, if a folder is found via name then its folder id is returned. In case the folder is not present, then folder id is null in response.

private String searchFolderId(String folderName, Drive service) throws Exception {
  return searchFolderId(null, String folderName, Drive service);
}

private String searchFolderId(String parentId, String folderName, Drive service) throws Exception {
  String folderId = null;
  String pageToken = null;
  FileList result = null;

  File fileMetadata = new File();
  fileMetadata.setMimeType("application/vnd.google-apps.folder");
  fileMetadata.setName(folderName);

  do {
     String query = " mimeType = 'application/vnd.google-apps.folder' ";
     if (parentId == null) {
        query = query + " and 'root' in parents";
     } else {
        query = query + " and '" + parentId + "' in parents";
     }
     result = service.files().list().setQ(query)
           .setSpaces("drive")
           .setFields("nextPageToken, files(id, name)")
           .setPageToken(pageToken)
           .execute();

     for (File file : result.getFiles()) {
        if (file.getName().equalsIgnoreCase(folderName)) {
           folderId = file.getId();
        }
     }
     pageToken = result.getNextPageToken();
  } while (pageToken != null && folderId == null);

  return folderId;
}

Google Drive create folder if not exists using Java client

In Google Drive, you can create a folder if it does not exist. Google Drive Client library provides a “create” method, which accepts file’s metadata class “File”. In fileMetadata, you need to set mime-type, name of folder and optional parent folder id (if required). Once you call execute method on create method your folder gets created in Google Drive.

Following code snippet shows how you can create a folder if it does not exist in Google Drive.

private String findOrCreateFolder(String parentId, String folderName, Drive driveInstance) throws Exception {
  String folderId = searchFolderId(parentId, folderName, driveInstance);
  // Folder already exists, so return id
  if (folderId != null) {
     return folderId;
  }
  //Folder dont exists, create it and return folderId
  File fileMetadata = new File();
  fileMetadata.setMimeType("application/vnd.google-apps.folder");
  fileMetadata.setName(folderName);
 
  if (parentId != null) {
     fileMetadata.setParents(Collections.singletonList(parentId));
  }
  return driveInstance.files().create(fileMetadata)
        .setFields("id")
        .execute()
        .getId();
}

Google Drive create all folders in path using Java client

In Google Drive you can create a complete nested path for a folder. Google Drive does not have direct support for the nested path, but it provides an alternative step by step which you can construct the required path. Treat each section of folder path as an independent folder and search sequentially which folder is present or which is not. The folder which is not found, can be created there and search can be performed at the next level.

For example, if we want to create a path “/folder1/folder2/folder3” in Google Drive. Then we can split the path with a separator in an array of strings. Then from the first folder we will start checking in a recursive way, if the folder is not present then we create it and move forward.

public String getFolderId(String path) throws Exception {
  String parentId = null;
  String[] folderNames = path.split("/");

  Drive driveInstance = googleDriveManager.getInstance();
  for (String name : folderNames) {
     parentId = findOrCreateFolder(parentId, name, driveInstance);
  }
  return parentId;
}

Google Drive upload file to a folder using Java client

Google Drive provides a create method to upload files to Google Drive, which takes file InputStreamContent as parameter which represents content type and InputStream of target file. If a file needs to be uploaded in a specific folder then in FileMetaData, that folder’s id can be mentioned as parent.

In the following example, we are passing a multipart file instance from rest api and the desired path where the file should be uploaded. From the rest api we will upload the file and mention the folder path where this file should be uploaded.

// FileManager.java
public String uploadFile(MultipartFile file, String filePath) {
  try {
     String folderId = getFolderId(filePath);
     if (null != file) {
        File fileMetadata = new File();
        fileMetadata.setParents(Collections.singletonList(folderId));
        fileMetadata.setName(file.getOriginalFilename());
        File uploadFile = googleDriveManager.getInstance()
              .files()
              .create(fileMetadata, new InputStreamContent(
                    file.getContentType(),
                    new ByteArrayInputStream(file.getBytes()))
              )
              .setFields("id").execute();
        return uploadFile.getId();
     }
  } catch (Exception e) {
     log.error("Error: ", e);
  }
  return null;
}

// MainController.java
@PostMapping(value = "/upload",
     consumes = {MediaType.MULTIPART_FORM_DATA_VALUE},
     produces = {MediaType.APPLICATION_JSON_VALUE} )
public ResponseEntity<String> uploadSingleFileExample4(@RequestBody MultipartFile file,@RequestParam(required = false) String path) {
  log.info("Request contains, File: " + file.getOriginalFilename());
  String fileId = fileManager.uploadFile(file, path);
  if(fileId == null){
     return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
  }
  return ResponseEntity.ok("Success, FileId: "+ fileId);
}

Google Drive download file using Java client

Google Drive allows you to download any file present in Google Drive, the client library provides the methods named get and executeMediaAndDownloadTo. If you need to download multiple files, then you have to download files one by one  and then zip together these files. Google Drive client libraries don’t support the Zipped file downloading feature. In Google drive, every file has a unique file id, you need to use this file id to download these files.

In the following example, we are passing the file id from api and then using the Google Drive service instance we can get the file and map it to the output stream in response.

// FileManager.java
public void downloadFile(String id, OutputStream outputStream) throws IOException, GeneralSecurityException {
  if (id != null) {
     String fileId = id;
     googleDriveManager.getInstance().files().get(fileId).executeMediaAndDownloadTo(outputStream);
  }
}

// MainController.java
@GetMapping("/download/{id}")
public void download(@PathVariable String id, HttpServletResponse response) throws IOException, GeneralSecurityException {
  fileManager.downloadFile(id, response.getOutputStream());
}

Google Drive delete file using Java client

Google Drive stores all files with unique file id, Google Drive client library can delete the files using these file ids. Google Drive client library has a delete method which accepts file id and on execution deletes the file from Google Drive.

In the following example, we are creating a rest api which accepts file id and then deletes the file using that file id from Google Drive.

// FileManager.java
public void deleteFile(String fileId) throws Exception {
  googleDriveManager.getInstance().files().delete(fileId).execute();
}

// MainController.java
@GetMapping("/delete/{id}")
public void delete(@PathVariable String id) throws Exception {
  fileManager.deleteFile(id);
}

Source code

All the examples mentioned here are present in git repository You can download the source code from github. In the example project the credentials file is blank for security reasons, you need to create and add your own credentials file there.

Conclusion

We hope this article will help you understand the file operations in Google Drive Java client library with Spring boot. If you want to learn more on uploading files, then you can check the article on Spring Boot multipart file upload examples.