How to Upload and download and delete and setlifycle in GCS Bucket?
1)implemented Upload API for uploading various file types (CSV,HTML,JSON,images)
2)implemented download API to download the file from GCS bucket
whenever we upload using content type and use the same info to download the file , I am says it’s html or CSV or json or image and download the content saved in the same format. so that user can easily open and view downloaded information
3) Implemented delete API for delete the GCS bucket
4) implemented Lifecycle API for GCS bucket
UPLOAD API :
// Initialize logger
private static final Logger logger = LoggerFactory.getLogger(FileUploadController.class);
@PostMapping("/upload")
public String uploadFile(
@RequestParam(value = "projectId", required = false) String projectId,
@RequestParam(value = "bucketName", required = false) String bucketName,
@RequestParam(value = "file", required = true) MultipartFile file) throws Exception {
// Log entry into the method
logger.info("Received file upload request - ProjectId: {}, BucketName: {}, FileName: {}, ContentType: {}, Size: {} bytes",
projectId, bucketName, file.getOriginalFilename(), file.getContentType(), file.getSize());
try {
// Initialize Google Cloud Storage
logger.debug("Initializing GCS client with projectId: {}", projectId);
Storage storage = StorageOptions.newBuilder()
.setProjectId(projectId)
.build()
.getService();
// Get bucket
logger.debug("Fetching bucket: {}", bucketName);
Bucket bucket = storage.get(bucketName);
if (bucket == null) {
logger.error("Bucket not found: {}", bucketName);
throw new IllegalArgumentException("Bucket not found: " + bucketName);
}
// Upload file to GCS
logger.info("Uploading file: {} to bucket: {}", file.getOriginalFilename(), bucketName);
Blob blob = bucket.create(
file.getOriginalFilename(),
file.getInputStream(),
file.getContentType()
);
// Log success
logger.info("File uploaded successfully - BlobName: {}, Size: {} bytes", blob.getName(), blob.getSize());
return "File uploaded successfully: " + blob.getName();
} catch (Exception e) {
// Log error details
logger.error("Failed to upload file: {}. Error: {}", file.getOriginalFilename(), e.getMessage(), e);
throw e; // Re-throw the exception for proper HTTP error handling
}
}
Download API :
@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(
@RequestParam String projectId,
@RequestParam String bucketName,
@RequestParam String objectName) throws Exception {
// Log the incoming request
logger.info("Received file download request - ProjectId: {}, BucketName: {}, ObjectName: {}",
projectId, bucketName, objectName);
try {
// Initialize Google Cloud Storage client
logger.debug("Initializing GCS client with projectId: {}", projectId);
Storage storage = StorageOptions.newBuilder()
.setProjectId(projectId)
.build()
.getService();
// Get the bucket
logger.debug("Fetching bucket: {}", bucketName);
Bucket bucket = storage.get(bucketName);
if (bucket == null) {
logger.error("Bucket not found: {} in project: {}", bucketName, projectId);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
// Get the blob (file)
logger.debug("Fetching object: {} from bucket: {}", objectName, bucketName);
Blob blob = bucket.get(objectName);
if (blob publications null) {
logger.error("File not found: {}/{} in project: {}", bucketName, objectName, projectId);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
// Retrieve file content and metadata
byte[] content = blob.getContent();
String contentType = blob.getContentType();
logger.info("File retrieved - ObjectName: {}, ContentType: {}, Size: {} bytes, ProjectId: {}",
objectName, contentType, content.length, projectId);
// Set up HTTP headers for download
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(contentType));
headers.setContentLength(content.length);
headers.setContentDispositionFormData("attachment", objectName); // Forces download with original filename
// Return the file as a ResponseEntity
logger.info("Sending file: {} to client", objectName);
return new ResponseEntity<>(content, headers, HttpStatus.OK);
} catch (Exception e) {
logger.error("Failed to download file: {}/{} in project: {}. Error: {}",
bucketName, objectName, projectId, e.getMessage(), e);
throw e; // Re-throw for proper error handling (e.g., global exception handler)
}
}
DELETE API :
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.cloud.storage.*;
@RestController
public class FileDeleteController {
private static final Logger logger = LoggerFactory.getLogger(FileDeleteController.class);
@DeleteMapping("/delete")
public String deleteFile(
@RequestParam(value = "projectId", required = false) String projectId,
@RequestParam(value = "bucketName", required = false) String bucketName,
@RequestParam(value = "objectName", required = true) String objectName) throws Exception {
// Log the incoming request
logger.info("Received file delete request - ProjectId: {}, BucketName: {}, ObjectName: {}",
projectId, bucketName, objectName);
try {
// Validate inputs
if (bucketName == null || bucketName.isBlank()) {
logger.error("Bucket name is required but was null or blank");
return "Error: Bucket name is required";
}
if (objectName == null || objectName.isBlank()) { // Redundant due to required=true, but added for safety
logger.error("Object name is required but was null or blank");
return "Error: Object name is required";
}
// Initialize Google Cloud Storage client
logger.debug("Initializing GCS client with projectId: {}", projectId);
Storage storage = StorageOptions.newBuilder()
.setProjectId(projectId)
.build()
.getService();
// Get the bucket
logger.debug("Fetching bucket: {}", bucketName);
Bucket bucket = storage.get(bucketName);
if (bucket == null) {
logger.error("Bucket not found: {} in project: {}", bucketName, projectId);
return "Error: Bucket not found: " + bucketName;
}
// Get the blob (file)
logger.debug("Fetching object: {} from bucket: {}", objectName, bucketName);
Blob blob = bucket.get(objectName);
if (blob == null) {
logger.error("File not found in GCS bucket: {}/{} in project: {}",
bucketName, objectName, projectId);
return "File not found: " + objectName;
}
// Attempt to delete the file
logger.info("Attempting to delete file: {} from bucket: {}", objectName, bucketName);
boolean deleted = blob.delete();
if (deleted) {
logger.info("File deleted successfully from GCS: {}/{} in project: {}",
bucketName, objectName, projectId);
return "File deleted successfully: " + objectName;
} else {
logger.error("Failed to delete file from GCS: {}/{} in project: {}",
bucketName, objectName, projectId);
return "Failed to delete file: " + objectName;
}
} catch (Exception e) {
logger.error("Error deleting file: {}/{} in project: {}. Exception: {}",
bucketName, objectName, projectId, e.getMessage(), e);
throw e; // Re-throw for proper error handling (e.g., global exception handler)
}
}
}
Set Lifecycle
@PostMapping("/setLifecycleRules")
public ResponseEntity<String> setLifecycleRules(
@RequestParam("bucketName") String bucketName,
@RequestParam(value = "deleteAfterDays", required = false) Integer deleteAfterDays,
@RequestParam(value = "updateStorageClassAfterDays", required = false) Integer updateStorageClassAfterDays,
@RequestParam(value = "newStorageClass", required = false) String newStorageClass) {
// Log the incoming request with all parameters
logger.info("Received lifecycle rules request - BucketName: {}, DeleteAfterDays: {}, UpdateStorageClassAfterDays: {}, NewStorageClass: {}",
bucketName, deleteAfterDays, updateStorageClassAfterDays, newStorageClass);
try {
// Call the service method
logger.debug("Calling GCS service to set lifecycle rules for bucket: {}", bucketName);
gcsService.setLifecycleRules(bucketName, deleteAfterDays, updateStorageClassAfterDays, newStorageClass);
// Log success
logger.info("Successfully set lifecycle rules for bucket: {}", bucketName);
return ResponseEntity.ok("Lifecycle rules set successfully");
} catch (IllegalArgumentException e) {
// Handle validation errors from the service
logger.error("Invalid request while setting lifecycle rules for bucket: {}. Error: {}",
bucketName, e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Invalid request: " + e.getMessage());
} catch (StorageException e) {
// Handle GCS-specific errors (e.g., permissions, network issues)
logger.error("Storage error while setting lifecycle rules for bucket: {}. Error: {}",
bucketName, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to set lifecycle rules due to storage error: " + e.getMessage());
} catch (Exception e) {
// Catch unexpected errors
logger.error("Unexpected error while setting lifecycle rules for bucket: {}. Error: {}",
bucketName, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An unexpected error occurred: " + e.getMessage());
}
}
Service Implementation class:
public void setLifecycleRules(String bucketName, Integer deleteAfterDays, Integer updateStorageClassAfterDays, String newStorageClass) {
if (bucketName == null || bucketName.isBlank()) {
throw new IllegalArgumentException("Bucket name is required");
}
Bucket bucket = storage.get(bucketName, Storage.BucketOption.fields(Storage.BucketField.LIFECYCLE));
if (bucket == null) {
throw new IllegalArgumentException("Bucket not found: " + bucketName);
}
List<BucketInfo.LifecycleRule> rules = new ArrayList<>();
if (deleteAfterDays != null && deleteAfterDays > 0) {
rules.add(new BucketInfo.LifecycleRule(
BucketInfo.LifecycleRule.LifecycleAction.newDeleteAction(),
BucketInfo.LifecycleRule.LifecycleCondition.newBuilder().setAge(deleteAfterDays).build()
));
}
if (updateStorageClassAfterDays != null && updateStorageClassAfterDays > 0 && newStorageClass != null && StringUtils.isNotBlank(newStorageClass)) {
try {
StorageClass storageClass = StorageClass.valueOf(newStorageClass.toUpperCase());
rules.add(new BucketInfo.LifecycleRule(
BucketInfo.LifecycleRule.LifecycleAction.newSetStorageClassAction(storageClass),
BucketInfo.LifecycleRule.LifecycleCondition.newBuilder().setAge(updateStorageClassAfterDays).build()
));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid storage class: " + newStorageClass, e);
}
}
if (rules.isEmpty()) {
throw new IllegalArgumentException("At least one lifecycle rule must be specified...");
}
bucket.toBuilder().setLifecycleRules(rules).build().update();
}
Test Cases for Implementation code :
@Test
public void testSetLifecycleRules_DeleteRuleOnly() {
when(storage.get(anyString(), any())).thenReturn(bucket);
when(bucket.toBuilder()).thenReturn(bucketBuilder);
when(bucketBuilder.setLifecycleRules(any())).thenReturn(bucketBuilder);
when(bucketBuilder.build()).thenReturn(bucket);
googleCloudStorageServiceImpl.setLifecycleRules("my-bucket", 30, null, null);
ArgumentCaptor<List<BucketInfo.LifecycleRule>> rulesCaptor = ArgumentCaptor.forClass(List.class);
verify(bucketBuilder).setLifecycleRules(rulesCaptor.capture());
List<BucketInfo.LifecycleRule> rules = rulesCaptor.getValue();
assertEquals(1, rules.size());
assertEquals(BucketInfo.LifecycleRule.LifecycleAction.newDeleteAction().getActionType(),
rules.get(0).getAction().getActionType());
}
@Test
void testSetLifecycleRules_StorageClassRuleOnly() {
// Setup
when(storage.get(anyString(), any())).thenReturn(bucket);
when(bucket.toBuilder()).thenReturn(bucketBuilder);
when(bucketBuilder.setLifecycleRules(any())).thenReturn(bucketBuilder);
when(bucketBuilder.build()).thenReturn(bucket);
// Action
googleCloudStorageServiceImpl.setLifecycleRules("my-bucket", null, 90, "NEARLINE");
// Verify
ArgumentCaptor<List<BucketInfo.LifecycleRule>> rulesCaptor = ArgumentCaptor.forClass(List.class);
verify(bucketBuilder).setLifecycleRules(rulesCaptor.capture());
verify(bucketBuilder).build();
verify(bucket).update();
List<BucketInfo.LifecycleRule> rules = rulesCaptor.getValue();
assertEquals(1, rules.size());
BucketInfo.LifecycleRule rule = rules.get(0);
assertEquals("SetStorageClass", rule.getAction().getActionType());
assertEquals(StorageClass.NEARLINE, ((BucketInfo.LifecycleRule.SetStorageClassAction) rule.getAction()).getStorageClass());
assertEquals(90, rule.getCondition().getAge());
}
@Test
void testSetLifecycleRules_BothRules() {
// Setup
when(storage.get(anyString(), any())).thenReturn(bucket);
when(bucket.toBuilder()).thenReturn(bucketBuilder);
when(bucketBuilder.setLifecycleRules(any())).thenReturn(bucketBuilder);
when(bucketBuilder.build()).thenReturn(bucket);
// Action
googleCloudStorageServiceImpl.setLifecycleRules("my-bucket", 30, 90, "COLDLINE");
// Verify
ArgumentCaptor<List<BucketInfo.LifecycleRule>> rulesCaptor = ArgumentCaptor.forClass(List.class);
verify(bucketBuilder).setLifecycleRules(rulesCaptor.capture());
verify(bucketBuilder).build();
verify(bucket).update();
List<BucketInfo.LifecycleRule> rules = rulesCaptor.getValue();
assertEquals(2, rules.size());
BucketInfo.LifecycleRule deleteRule = rules.get(0);
assertEquals("Delete", deleteRule.getAction().getActionType());
assertEquals(30, deleteRule.getCondition().getAge());
BucketInfo.LifecycleRule storageRule = rules.get(1);
assertEquals("SetStorageClass", storageRule.getAction().getActionType());
assertEquals(StorageClass.COLDLINE, ((BucketInfo.LifecycleRule.SetStorageClassAction) storageRule.getAction()).getStorageClass());
assertEquals(90, storageRule.getCondition().getAge());
}
POM.XML :
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>cloud-storage-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Cloud Storage Service</name>
<description>Spring Boot service for Google Cloud Storage</description>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.1.0</spring-boot.version>
<lombok.version>1.18.28</lombok.version>
<google-cloud-storage.version>2.21.0</google-cloud-storage.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SLF4J for logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<!-- Google Cloud Storage SDK -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>${google-cloud-storage.version}</version>
</dependency>
<!-- Google Cloud NIO (for working with Cloud Storage as a filesystem) -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-nio</artifactId>
<version>0.126.15</version>
</dependency>
<!-- Lombok for reducing boilerplate code -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Spring Boot Starter Test (for testing) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito for testing -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Generate-PresignedURL :
@GetMapping(path = "/presigned-url", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<UrlResponse> preSignedUrl(
@RequestParam(value = "bucketName", required = true) String bucketName,
@RequestParam(value = "projectId", required = true) String projectId,
@RequestParam(value = "method", required = true) String method,
@RequestParam(value = "filepath", required = true) String filepath) {
try {
LOGGER.info("Generating pre-signed URL for: method={}, filepath={}, bucketName={}, provider={}, projectId={}",
method, filepath, bucketName, provider, projectId);
// Validate HTTP method
HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
// Validate provider - only accepting GCS- not using below code
Provider serviceName = Provider.valueOf(provider.toUpperCase());
if (serviceName != Provider.GCS) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new UrlResponse("Only GCS provider is supported. Invalid provider: " + provider));
}
// Generate pre-signed URL for GCS
Storage storage = StorageOptions.newBuilder()
.setProjectId(projectId)
.setCredentials(GoogleCredentials.getApplicationDefault())
.build()
.getService();
UrlResponse urlResponse = gcsService.generatePreSignedUrl(filepath, bucketName, httpMethod, storage);
return ResponseEntity.ok(urlResponse);
} catch (IllegalArgumentException e) {
LOGGER.error("Invalid input parameter: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new UrlResponse("Invalid input parameter: " + e.getMessage()));
} catch (IOException e) {
LOGGER.error("Failed to initialize GCS storage: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new UrlResponse("Failed to initialize storage: " + e.getMessage()));
} catch (Exception e) {
LOGGER.error("Unexpected error: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new UrlResponse("Unexpected error: " + e.getMessage()));
}
}
Learn more How to Upload and download and delete and setlifycle in GCS Bucket?