Google Cloud Run is a fully managed serverless platform that automatically scales your stateless containers. This guide covers building, deploying, and optimizing Java applications for Cloud Run.
Core Concepts
Why Cloud Run for Java?
- Serverless: No infrastructure management
- Auto-scaling: Scales to zero when not in use
- Pay-per-use: Only pay for request processing time
- Container-based: Use any Java framework or library
- Fast deployment: Quick iterations and deployments
Key Considerations for Java on Cloud Run:
- Cold Start Optimization: Minimize startup time
- Memory Management: Configure appropriate memory limits
- Stateless Design: No local storage or session state
- Health Checks: Implement proper readiness/liveness probes
- Concurrency: Handle multiple requests per container
Dependencies and Setup
1. Maven Dependencies
<properties>
<spring-boot.version>3.1.0</spring-boot.version>
<jib-maven-plugin.version>3.4.0</jib-maven-plugin.version>
<google-cloud.version>2.20.0</google-cloud.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Actuator for Health Checks -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
<optional>true</optional>
</dependency>
<!-- Google Cloud Libraries -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>${google-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-secretmanager</artifactId>
<version>${google-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-logging</artifactId>
<version>${google-cloud.version}</version>
</dependency>
<!-- Database (PostgreSQL) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<!-- Redis for Caching -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers for Integration Tests -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.18.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.18.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- Jib for Container Building -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>${jib-maven-plugin.version}</version>
<configuration>
<from>
<image>eclipse-temurin:21-jre</image>
</from>
<to>
<image>gcr.io/${google.cloud.project}/${project.artifactId}:${project.version}</image>
</to>
<container>
<jvmFlags>
<jvmFlag>-Xmx512m</jvmFlag>
<jvmFlag>-Xms256m</jvmFlag>
<jvmFlag>-XX:+UseG1GC</jvmFlag>
<jvmFlag>-XX:MaxRAMPercentage=75.0</jvmFlag>
</jvmFlags>
<environment>
<SPRING_PROFILES_ACTIVE>cloudrun</SPRING_PROFILES_ACTIVE>
</environment>
<ports>
<port>8080</port>
</ports>
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
</container>
</configuration>
</plugin>
</plugins>
</build>
2. Google Cloud SDK Setup
# Install Google Cloud SDK curl https://sdk.cloud.google.com | bash exec -l $SHELL # Initialize and authenticate gcloud init gcloud auth configure-docker # Set project gcloud config set project YOUR_PROJECT_ID
Application Implementation
1. Main Application Class
package com.example.cloudrun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(CloudRunProperties.class)
public class CloudRunApplication {
public static void main(String[] args) {
SpringApplication.run(CloudRunApplication.class, args);
}
}
2. Cloud Run Configuration
package com.example.cloudrun.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cloudrun")
public class CloudRunProperties {
private String serviceName;
private String revision;
private String configuration;
private int maxInstances = 100;
private int concurrency = 80;
private int timeoutSeconds = 300;
private String region = "us-central1";
// Getters and Setters
public String getServiceName() { return serviceName; }
public void setServiceName(String serviceName) { this.serviceName = serviceName; }
public String getRevision() { return revision; }
public void setRevision(String revision) { this.revision = revision; }
public String getConfiguration() { return configuration; }
public void setConfiguration(String configuration) { this.configuration = configuration; }
public int getMaxInstances() { return maxInstances; }
public void setMaxInstances(int maxInstances) { this.maxInstances = maxInstances; }
public int getConcurrency() { return concurrency; }
public void setConcurrency(int concurrency) { this.concurrency = concurrency; }
public int getTimeoutSeconds() { return timeoutSeconds; }
public void setTimeoutSeconds(int timeoutSeconds) { this.timeoutSeconds = timeoutSeconds; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
}
3. Application Configuration
# application-cloudrun.yml
server:
port: 8080
servlet:
context-path: /
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
min-response-size: 1024
spring:
application:
name: cloudrun-demo
datasource:
url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/cloudrun}
username: ${DATABASE_USERNAME:postgres}
password: ${DATABASE_PASSWORD:password}
hikari:
maximum-pool-size: 5
minimum-idle: 1
connection-timeout: 30000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
jdbc:
lob:
non_contextual_creation: true
show_sql: false
format_sql: false
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 1
max-wait: -1ms
cloud:
gcp:
project-id: ${GOOGLE_CLOUD_PROJECT}
credentials:
location: ${GOOGLE_APPLICATION_CREDENTIALS:}
management:
endpoints:
web:
exposure:
include: health,info,metrics,env
base-path: /actuator
endpoint:
health:
show-details: always
show-components: always
probes:
enabled: true
metrics:
enabled: true
cloudrun:
service-name: ${K_SERVICE:local}
revision: ${K_REVISION:local}
configuration: ${K_CONFIGURATION:local}
max-instances: ${MAX_INSTANCES:100}
concurrency: ${CONCURRENCY:80}
timeout-seconds: ${TIMEOUT_SECONDS:300}
region: ${REGION:us-central1}
logging:
level:
com.example.cloudrun: INFO
org.hibernate.SQL: WARN
org.springframework.cloud.gcp: INFO
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
# Optimize for Cloud Run
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
threads:
virtual:
enabled: true
4. Health Check Configuration
package com.example.cloudrun.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Map;
@Component
public class CloudRunHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
private final CloudRunMetadata cloudRunMetadata;
public CloudRunHealthIndicator(DataSource dataSource, CloudRunMetadata cloudRunMetadata) {
this.dataSource = dataSource;
this.cloudRunMetadata = cloudRunMetadata;
}
@Override
public Health health() {
try {
// Check database connectivity
boolean dbHealthy = checkDatabaseHealth();
// Build health details
Health.Builder healthBuilder = dbHealthy ?
Health.up() : Health.down();
healthBuilder
.withDetail("service", cloudRunMetadata.getServiceName())
.withDetail("revision", cloudRunMetadata.getRevision())
.withDetail("region", cloudRunMetadata.getRegion())
.withDetail("timestamp", System.currentTimeMillis())
.withDetails(buildHealthDetails(dbHealthy));
return healthBuilder.build();
} catch (Exception e) {
return Health.down(e)
.withDetail("service", cloudRunMetadata.getServiceName())
.withDetail("error", e.getMessage())
.build();
}
}
private boolean checkDatabaseHealth() {
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
return statement.execute("SELECT 1");
} catch (Exception e) {
return false;
}
}
private Map<String, Object> buildHealthDetails(boolean dbHealthy) {
return Map.of(
"database", dbHealthy ? "CONNECTED" : "DISCONNECTED",
"memory_usage", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(),
"available_processors", Runtime.getRuntime().availableProcessors(),
"uptime", ManagementFactory.getRuntimeMXBean().getUptime()
);
}
}
@Component
class CloudRunMetadata {
@Value("${K_SERVICE:local}")
private String serviceName;
@Value("${K_REVISION:local}")
private String revision;
@Value("${REGION:us-central1}")
private String region;
public String getServiceName() { return serviceName; }
public String getRevision() { return revision; }
public String getRegion() { return region; }
}
5. Domain Models
package com.example.cloudrun.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@NotBlank
@Size(max = 100)
@Column(nullable = false)
private String firstName;
@NotBlank
@Size(max = 100)
@Column(nullable = false)
private String lastName;
@NotBlank
@Email
@Size(max = 255)
@Column(nullable = false, unique = true)
private String email;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private UserStatus status = UserStatus.ACTIVE;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Constructors
public User() {}
public User(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
// Getters and Setters
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public UserStatus getStatus() { return status; }
public void setStatus(UserStatus status) { this.status = status; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}
enum UserStatus {
ACTIVE, INACTIVE, SUSPENDED
}
// Data Transfer Objects
public class UserResponse {
private final UUID id;
private final String firstName;
private final String lastName;
private final String email;
private final UserStatus status;
private final LocalDateTime createdAt;
public UserResponse(User user) {
this.id = user.getId();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.email = user.getEmail();
this.status = user.getStatus();
this.createdAt = user.getCreatedAt();
}
// Getters
public UUID getId() { return id; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String getEmail() { return email; }
public UserStatus getStatus() { return status; }
public LocalDateTime getCreatedAt() { return createdAt; }
}
public class CreateUserRequest {
@NotBlank
@Size(max = 100)
private String firstName;
@NotBlank
@Size(max = 100)
private String lastName;
@NotBlank
@Email
@Size(max = 255)
private String email;
// Getters and Setters
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
6. Repository Layer
package com.example.cloudrun.repository;
import com.example.cloudrun.model.User;
import com.example.cloudrun.model.UserStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
Optional<User> findByEmail(String email);
List<User> findByStatus(UserStatus status);
Page<User> findByStatus(UserStatus status, Pageable pageable);
@Query("SELECT u FROM User u WHERE LOWER(u.firstName) LIKE LOWER(CONCAT('%', :name, '%')) OR LOWER(u.lastName) LIKE LOWER(CONCAT('%', :name, '%'))")
Page<User> findByNameContainingIgnoreCase(@Param("name") String name, Pageable pageable);
boolean existsByEmail(String email);
long countByStatus(UserStatus status);
}
7. Service Layer
package com.example.cloudrun.service;
import com.example.cloudrun.model.User;
import com.example.cloudrun.model.UserStatus;
import com.example.cloudrun.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.UUID;
@Service
@Transactional
public class UserService {
private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
private final UserRepository userRepository;
private final CloudRunMetrics metrics;
public UserService(UserRepository userRepository, CloudRunMetrics metrics) {
this.userRepository = userRepository;
this.metrics = metrics;
}
@Transactional(readOnly = true)
public Page<User> getAllUsers(Pageable pageable) {
LOG.debug("Fetching users page: {}", pageable);
metrics.recordUserQuery();
return userRepository.findAll(pageable);
}
@Transactional(readOnly = true)
@Cacheable(value = "users", key = "#id")
public Optional<User> getUserById(UUID id) {
LOG.debug("Fetching user by ID: {}", id);
metrics.recordUserQuery();
return userRepository.findById(id);
}
@Transactional(readOnly = true)
public Optional<User> getUserByEmail(String email) {
LOG.debug("Fetching user by email: {}", email);
metrics.recordUserQuery();
return userRepository.findByEmail(email);
}
public User createUser(User user) {
LOG.info("Creating new user with email: {}", user.getEmail());
if (userRepository.existsByEmail(user.getEmail())) {
throw new UserServiceException("User with email " + user.getEmail() + " already exists");
}
User savedUser = userRepository.save(user);
metrics.recordUserCreated();
LOG.info("Successfully created user: {}", savedUser.getId());
return savedUser;
}
public User updateUser(UUID id, User userUpdate) {
LOG.info("Updating user: {}", id);
User existingUser = userRepository.findById(id)
.orElseThrow(() -> new UserServiceException("User not found: " + id));
// Update fields if provided
if (userUpdate.getFirstName() != null) {
existingUser.setFirstName(userUpdate.getFirstName());
}
if (userUpdate.getLastName() != null) {
existingUser.setLastName(userUpdate.getLastName());
}
if (userUpdate.getStatus() != null) {
existingUser.setStatus(userUpdate.getStatus());
}
User updatedUser = userRepository.save(existingUser);
metrics.recordUserUpdated();
LOG.info("Successfully updated user: {}", id);
return updatedUser;
}
public void deleteUser(UUID id) {
LOG.info("Deleting user: {}", id);
User user = userRepository.findById(id)
.orElseThrow(() -> new UserServiceException("User not found: " + id));
userRepository.delete(user);
metrics.recordUserDeleted();
LOG.info("Successfully deleted user: {}", id);
}
@Transactional(readOnly = true)
public long getActiveUsersCount() {
long count = userRepository.countByStatus(UserStatus.ACTIVE);
metrics.recordUserQuery();
return count;
}
public User deactivateUser(UUID id) {
LOG.info("Deactivating user: {}", id);
User user = userRepository.findById(id)
.orElseThrow(() -> new UserServiceException("User not found: " + id));
user.setStatus(UserStatus.INACTIVE);
User deactivatedUser = userRepository.save(user);
metrics.recordUserDeactivated();
return deactivatedUser;
}
}
class UserServiceException extends RuntimeException {
public UserServiceException(String message) {
super(message);
}
public UserServiceException(String message, Throwable cause) {
super(message, cause);
}
}
8. REST Controllers
package com.example.cloudrun.controller;
import com.example.cloudrun.model.CreateUserRequest;
import com.example.cloudrun.model.User;
import com.example.cloudrun.model.UserResponse;
import com.example.cloudrun.service.UserService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private static final Logger LOG = LoggerFactory.getLogger(UserController.class);
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<Page<UserResponse>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "createdAt") String sortBy,
@RequestParam(defaultValue = "desc") String sortDirection) {
LOG.info("Fetching users - page: {}, size: {}, sort: {}", page, size, sortBy);
Sort sort = Sort.by(Sort.Direction.fromString(sortDirection), sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
Page<UserResponse> users = userService.getAllUsers(pageable)
.map(UserResponse::new);
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUserById(@PathVariable UUID id) {
LOG.info("Fetching user by ID: {}", id);
return userService.getUserById(id)
.map(user -> ResponseEntity.ok(new UserResponse(user)))
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
LOG.info("Creating user with email: {}", request.getEmail());
User user = new User(request.getFirstName(), request.getLastName(), request.getEmail());
User createdUser = userService.createUser(user);
return ResponseEntity.status(HttpStatus.CREATED)
.body(new UserResponse(createdUser));
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(
@PathVariable UUID id,
@Valid @RequestBody CreateUserRequest request) {
LOG.info("Updating user: {}", id);
User userUpdate = new User(request.getFirstName(), request.getLastName(), request.getEmail());
User updatedUser = userService.updateUser(id, userUpdate);
return ResponseEntity.ok(new UserResponse(updatedUser));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable UUID id) {
LOG.info("Deleting user: {}", id);
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
@PostMapping("/{id}/deactivate")
public ResponseEntity<UserResponse> deactivateUser(@PathVariable UUID id) {
LOG.info("Deactivating user: {}", id);
User deactivatedUser = userService.deactivateUser(id);
return ResponseEntity.ok(new UserResponse(deactivatedUser));
}
@GetMapping("/stats/active-count")
public ResponseEntity<ActiveUsersCountResponse> getActiveUsersCount() {
long count = userService.getActiveUsersCount();
return ResponseEntity.ok(new ActiveUsersCountResponse(count));
}
}
class ActiveUsersCountResponse {
private final long activeUsersCount;
public ActiveUsersCountResponse(long activeUsersCount) {
this.activeUsersCount = activeUsersCount;
}
public long getActiveUsersCount() {
return activeUsersCount;
}
}
9. Google Cloud Integration Services
package com.example.cloudrun.integration;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.util.UUID;
@Service
public class CloudStorageService {
private static final Logger LOG = LoggerFactory.getLogger(CloudStorageService.class);
private final Storage storage;
@Value("${gcp.storage.bucket:default-bucket}")
private String bucketName;
public CloudStorageService(Storage storage) {
this.storage = storage;
}
public String uploadFile(InputStream fileStream, String contentType, String originalFilename) {
try {
String fileName = generateFileName(originalFilename);
BlobId blobId = BlobId.of(bucketName, fileName);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
.setContentType(contentType)
.build();
storage.createFrom(blobInfo, fileStream);
String publicUrl = String.format("https://storage.googleapis.com/%s/%s", bucketName, fileName);
LOG.info("File uploaded successfully: {}", publicUrl);
return publicUrl;
} catch (Exception e) {
LOG.error("Failed to upload file to Cloud Storage: {}", e.getMessage(), e);
throw new CloudStorageException("File upload failed", e);
}
}
public void deleteFile(String fileName) {
try {
boolean deleted = storage.delete(bucketName, fileName);
if (deleted) {
LOG.info("File deleted successfully: {}", fileName);
} else {
LOG.warn("File not found for deletion: {}", fileName);
}
} catch (Exception e) {
LOG.error("Failed to delete file from Cloud Storage: {}", e.getMessage(), e);
throw new CloudStorageException("File deletion failed", e);
}
}
private String generateFileName(String originalFilename) {
String extension = "";
if (originalFilename != null && originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
return "uploads/" + UUID.randomUUID() + extension;
}
}
class CloudStorageException extends RuntimeException {
public CloudStorageException(String message) {
super(message);
}
public CloudStorageException(String message, Throwable cause) {
super(message, cause);
}
}
package com.example.cloudrun.integration;
import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse;
import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import com.google.cloud.secretmanager.v1.SecretVersionName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class SecretManagerService {
private static final Logger LOG = LoggerFactory.getLogger(SecretManagerService.class);
public String getSecret(String secretId, String version) {
try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) {
String projectId = System.getenv("GOOGLE_CLOUD_PROJECT");
SecretVersionName secretVersionName = SecretVersionName.of(projectId, secretId, version);
AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);
String secretValue = response.getPayload().getData().toStringUtf8();
LOG.debug("Successfully retrieved secret: {}", secretId);
return secretValue;
} catch (IOException e) {
LOG.error("Failed to retrieve secret {}: {}", secretId, e.getMessage());
throw new SecretManagerException("Failed to retrieve secret: " + secretId, e);
}
}
public String getSecret(String secretId) {
return getSecret(secretId, "latest");
}
}
class SecretManagerException extends RuntimeException {
public SecretManagerException(String message) {
super(message);
}
public SecretManagerException(String message, Throwable cause) {
super(message, cause);
}
}
10. Metrics and Monitoring
package com.example.cloudrun.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class CloudRunMetrics {
private final Counter requestsCounter;
private final Counter userCreatedCounter;
private final Counter userUpdatedCounter;
private final Counter userDeletedCounter;
private final Counter userDeactivatedCounter;
private final Counter userQueryCounter;
private final Timer requestTimer;
public CloudRunMetrics(MeterRegistry meterRegistry) {
this.requestsCounter = Counter.builder("cloudrun.requests.total")
.description("Total number of HTTP requests")
.register(meterRegistry);
this.userCreatedCounter = Counter.builder("cloudrun.users.created")
.description("Number of users created")
.register(meterRegistry);
this.userUpdatedCounter = Counter.builder("cloudrun.users.updated")
.description("Number of users updated")
.register(meterRegistry);
this.userDeletedCounter = Counter.builder("cloudrun.users.deleted")
.description("Number of users deleted")
.register(meterRegistry);
this.userDeactivatedCounter = Counter.builder("cloudrun.users.deactivated")
.description("Number of users deactivated")
.register(meterRegistry);
this.userQueryCounter = Counter.builder("cloudrun.users.queries")
.description("Number of user queries")
.register(meterRegistry);
this.requestTimer = Timer.builder("cloudrun.request.duration")
.description("HTTP request duration")
.register(meterRegistry);
}
public void recordRequest() {
requestsCounter.increment();
}
public void recordUserCreated() {
userCreatedCounter.increment();
}
public void recordUserUpdated() {
userUpdatedCounter.increment();
}
public void recordUserDeleted() {
userDeletedCounter.increment();
}
public void recordUserDeactivated() {
userDeactivatedCounter.increment();
}
public void recordUserQuery() {
userQueryCounter.increment();
}
public void recordRequestTime(long duration, TimeUnit unit) {
requestTimer.record(duration, unit);
}
}
Docker Configuration
1. Dockerfile for Cloud Run
# Use Eclipse Temurin JRE for smaller image size FROM eclipse-temurin:21-jre-jammy as runtime # Install curl for health checks RUN apt-get update && \ apt-get install -y curl && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean # Create a non-root user RUN groupadd --system --gid 1000 appgroup && \ useradd --system --uid 1000 --gid appgroup appuser # Set working directory WORKDIR /app # Copy the JAR file COPY target/*.jar app.jar # Change ownership to non-root user RUN chown -R appuser:appgroup /app # Switch to non-root user USER appuser # Expose the port Cloud Run expects EXPOSE 8080 # Health check (Cloud Run uses startup and liveness probes) HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # Optimized JVM flags for Cloud Run ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom" # Start the application ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
2. Jib Configuration for Maven
<!-- Enhanced Jib Configuration -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<image>eclipse-temurin:21-jre-jammy</image>
<platforms>
<platform>
<architecture>amd64</architecture>
<os>linux</os>
</platform>
</platforms>
</from>
<to>
<image>gcr.io/${google.cloud.project}/${project.artifactId}</image>
<tags>
<tag>${project.version}</tag>
<tag>latest</tag>
</tags>
</to>
<container>
<user>1000:1000</user>
<workingDirectory>/app</workingDirectory>
<entrypoint>
<arg>java</arg>
<arg>-Xmx512m</arg>
<arg>-Xms256m</arg>
<arg>-XX:+UseG1GC</arg>
<arg>-XX:MaxRAMPercentage=75.0</arg>
<arg>-Djava.security.egd=file:/dev/./urandom</arg>
<arg>-jar</arg>
<arg>/app/app.jar</arg>
</entrypoint>
<ports>
<port>8080</port>
</ports>
<environment>
<SPRING_PROFILES_ACTIVE>cloudrun</SPRING_PROFILES_ACTIVE>
<JAVA_TOOL_OPTIONS>-XX:+ExitOnOutOfMemoryError</JAVA_TOOL_OPTIONS>
</environment>
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
<labels>
<maintainer>[email protected]</maintainer>
<version>${project.version}</version>
<commit-sha>${git.commit.id.abbrev}</commit-sha>
</labels>
</container>
</configuration>
</plugin>
Deployment Configuration
1. Cloud Run Service Definition
# service.yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: cloudrun-demo labels: app: cloudrun-demo environment: production spec: template: metadata: annotations: # Auto-scaling configuration autoscaling.knative.dev/maxScale: "100" autoscaling.knative.dev/minScale: "0" # Concurrency configuration run.googleapis.com/concurrency: "80" # CPU configuration run.googleapis.com/cpu-throttling: "true" # Startup configuration run.googleapis.com/startup-cpu-boost: "true" spec: containerConcurrency: 80 timeoutSeconds: 300 containers: - image: gcr.io/YOUR_PROJECT_ID/cloudrun-demo:latest ports: - containerPort: 8080 resources: limits: cpu: 1000m memory: 512Mi requests: cpu: 250m memory: 256Mi env: - name: SPRING_PROFILES_ACTIVE value: "cloudrun,production" - name: DATABASE_URL valueFrom: secretKeyRef: name: cloudrun-db-secret key: url - name: DATABASE_USERNAME valueFrom: secretKeyRef: name: cloudrun-db-secret key: username - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: cloudrun-db-secret key: password - name: REDIS_HOST value: "10.0.0.3" - name: MAX_INSTANCES value: "100" - name: CONCURRENCY value: "80" - name: TIMEOUT_SECONDS value: "300" startupProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 0 periodSeconds: 5 timeoutSeconds: 10 failureThreshold: 30 # 30 * 5 = 150 seconds max startup time livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 traffic: - percent: 100 latestRevision: true
2. Deployment Scripts
#!/bin/bash # deploy.sh set -e # Configuration PROJECT_ID="your-project-id" SERVICE_NAME="cloudrun-demo" REGION="us-central1" IMAGE_NAME="gcr.io/$PROJECT_ID/$SERVICE_NAME" echo "Building and deploying $SERVICE_NAME to Cloud Run..." # Build the container image echo "Building container image..." mvn compile jib:build -DskipTests # Deploy to Cloud Run echo "Deploying to Cloud Run..." gcloud run deploy $SERVICE_NAME \ --image $IMAGE_NAME \ --platform managed \ --region $REGION \ --allow-unauthenticated \ --memory 512Mi \ --cpu 1 \ --concurrency 80 \ --max-instances 100 \ --timeout 300s \ --set-env-vars=SPRING_PROFILES_ACTIVE=cloudrun,production \ --update-secrets=DATABASE_URL=cloudrun-db-secret:latest \ --update-secrets=DATABASE_USERNAME=cloudrun-db-secret:latest \ --update-secrets=DATABASE_PASSWORD=cloudrun-db-secret:latest # Get the service URL SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --region $REGION --format 'value(status.url)') echo "Service deployed successfully: $SERVICE_URL" # Run health check echo "Running health check..." curl -f "$SERVICE_URL/actuator/health" || exit 1 echo "Health check passed!"
3. Cloud Build Configuration
# cloudbuild.yaml steps: # Build the application - name: maven:3.9-eclipse-temurin-21 entrypoint: mvn args: ['clean', 'compile', '-DskipTests'] # Run tests - name: maven:3.9-eclipse-temurin-21 entrypoint: mvn args: ['test'] # Build container image - name: maven:3.9-eclipse-temurin-21 entrypoint: mvn args: ['compile', 'jib:build', '-DskipTests'] secretEnv: ['GOOGLE_APPLICATION_CREDENTIALS'] # Deploy to Cloud Run - name: 'gcr.io/cloud-builders/gcloud' args: - 'run' - 'deploy' - 'cloudrun-demo' - '--image=gcr.io/$PROJECT_ID/cloudrun-demo:latest' - '--region=us-central1' - '--platform=managed' - '--allow-unauthenticated' - '--memory=512Mi' - '--cpu=1' - '--concurrency=80' - '--max-instances=100' - '--timeout=300s' - '--set-env-vars=SPRING_PROFILES_ACTIVE=cloudrun,production' - '--update-secrets=DATABASE_URL=cloudrun-db-secret:latest' - '--update-secrets=DATABASE_USERNAME=cloudrun-db-secret:latest' - '--update-secrets=DATABASE_PASSWORD=cloudrun-db-secret:latest' # Secrets configuration availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/cloudbuild-sa-key/versions/latest env: 'GOOGLE_APPLICATION_CREDENTIALS' # Images to store in Container Registry images: - 'gcr.io/$PROJECT_ID/cloudrun-demo:latest' # Timeout and options timeout: 1800s options: logging: CLOUD_LOGGING_ONLY
Testing
1. Unit Tests
package com.example.cloudrun.service;
import com.example.cloudrun.model.User;
import com.example.cloudrun.model.UserStatus;
import com.example.cloudrun.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private CloudRunMetrics metrics;
@InjectMocks
private UserService userService;
@Test
void testCreateUser() {
// Given
User user = new User("John", "Doe", "[email protected]");
User savedUser = new User("John", "Doe", "[email protected]");
savedUser.setId(UUID.randomUUID());
when(userRepository.existsByEmail("[email protected]")).thenReturn(false);
when(userRepository.save(any(User.class))).thenReturn(savedUser);
// When
User result = userService.createUser(user);
// Then
assertNotNull(result);
assertNotNull(result.getId());
assertEquals("[email protected]", result.getEmail());
verify(userRepository).existsByEmail("[email protected]");
verify(userRepository).save(user);
verify(metrics).recordUserCreated();
}
@Test
void testGetUserById() {
// Given
UUID userId = UUID.randomUUID();
User user = new User("John", "Doe", "[email protected]");
user.setId(userId);
when(userRepository.findById(userId)).thenReturn(Optional.of(user));
// When
Optional<User> result = userService.getUserById(userId);
// Then
assertTrue(result.isPresent());
assertEquals(userId, result.get().getId());
verify(userRepository).findById(userId);
verify(metrics).recordUserQuery();
}
}
2. Integration Tests
package com.example.cloudrun.controller;
import com.example.cloudrun.model.CreateUserRequest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void testCreateUser() throws Exception {
CreateUserRequest request = new CreateUserRequest();
request.setFirstName("John");
request.setLastName("Doe");
request.setEmail("[email protected]");
mockMvc.perform(post("/api/v1/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.firstName").value("John"))
.andExpect(jsonPath("$.lastName").value("Doe"))
.andExpect(jsonPath("$.email").value("[email protected]"));
}
@Test
void testHealthCheck() throws Exception {
mockMvc.perform(get("/actuator/health"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"));
}
}
3. Test Configuration
# application-test.yml spring: datasource: url: jdbc:tc:postgresql:15:///testdb username: test password: test jpa: hibernate: ddl-auto: create-drop show-sql: true redis: host: localhost port: 6379 cloud: gcp: enabled: false logging: level: com.example.cloudrun: DEBUG
Performance Optimization
1. Cold Start Optimization
package com.example.cloudrun.optimization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
@Component
@Profile("cloudrun")
public class WarmupRunner implements CommandLineRunner {
private static final Logger LOG = LoggerFactory.getLogger(WarmupRunner.class);
private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;
private final RedisTemplate<String, String> redisTemplate;
public WarmupRunner(DataSource dataSource, JdbcTemplate jdbcTemplate,
RedisTemplate<String, String> redisTemplate) {
this.dataSource = dataSource;
this.jdbcTemplate = jdbcTemplate;
this.redisTemplate = redisTemplate;
}
@Override
public void run(String... args) throws Exception {
LOG.info("Starting application warmup...");
// Warm up database connections
warmupDatabase();
// Warm up Redis connection
warmupRedis();
// Warm up any other critical components
warmupComponents();
LOG.info("Application warmup completed");
}
private void warmupDatabase() {
try (Connection connection = dataSource.getConnection()) {
jdbcTemplate.execute("SELECT 1");
LOG.info("Database connection warmed up successfully");
} catch (Exception e) {
LOG.warn("Database warmup failed: {}", e.getMessage());
}
}
private void warmupRedis() {
try {
redisTemplate.opsForValue().get("warmup");
LOG.info("Redis connection warmed up successfully");
} catch (Exception e) {
LOG.warn("Redis warmup failed: {}", e.getMessage());
}
}
private void warmupComponents() {
// Warm up any other components that benefit from initialization
// This could include HTTP clients, thread pools, etc.
LOG.info("Component warmup completed");
}
}
2. Memory Optimization Configuration
package com.example.cloudrun.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfiguration {
@Bean(name = "taskExecutor")
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "false")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// Conservative settings for Cloud Run
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("CloudRunAsync-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
}
Monitoring and Logging
1. Structured Logging
package com.example.cloudrun.logging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
MDC.put("service", "cloudrun-demo");
long startTime = System.currentTimeMillis();
try {
LOG.info("Request started: {} {} from {}",
request.getMethod(),
request.getRequestURI(),
request.getRemoteAddr());
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
LOG.info("Request completed: {} {} - Status: {} - Duration: {}ms",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration);
MDC.clear();
}
}
}
2. Custom Metrics
package com.example.cloudrun.metrics;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class RuntimeMetrics {
private final AtomicInteger activeRequests = new AtomicInteger(0);
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
public RuntimeMetrics(MeterRegistry meterRegistry) {
// Active requests gauge
Gauge.builder("cloudrun.runtime.active.requests")
.description("Number of active HTTP requests")
.register(meterRegistry, activeRequests, AtomicInteger::get);
// Memory usage gauge
Gauge.builder("cloudrun.runtime.memory.used")
.description("Used memory in bytes")
.register(meterRegistry, this, RuntimeMetrics::getUsedMemory);
// Available memory gauge
Gauge.builder("cloudrun.runtime.memory.max")
.description("Max memory in bytes")
.register(meterRegistry, this, RuntimeMetrics::getMaxMemory);
}
public void incrementActiveRequests() {
activeRequests.incrementAndGet();
}
public void decrementActiveRequests() {
activeRequests.decrementAndGet();
}
private long getUsedMemory() {
return memoryMXBean.getHeapMemoryUsage().getUsed() +
memoryMXBean.getNonHeapMemoryUsage().getUsed();
}
private long getMaxMemory() {
return memoryMXBean.getHeapMemoryUsage().getMax() +
memoryMXBean.getNonHeapMemoryUsage().getMax();
}
@Scheduled(fixedRate = 30000) // Every 30 seconds
public void logRuntimeStats() {
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
LOG.info("Runtime stats - Memory: {}/{} MB, Active requests: {}",
usedMemory / 1024 / 1024,
maxMemory / 1024 / 1024,
activeRequests.get());
}
}
Best Practices
1. Environment-specific Configuration
# application-production.yml spring: datasource: hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 max-lifetime: 1800000 idle-timeout: 600000 redis: lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 jpa: properties: hibernate: connection: provider_disables_autocommit: true management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: never metrics: enabled: true logging: level: com.example.cloudrun: INFO org.hibernate: WARN org.springframework: INFO # Cloud Run specific optimizations spring: lifecycle: timeout-per-shutdown-phase: 30s threads: virtual: enabled: true
2. Security Configuration
package com.example.cloudrun.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// Cloud Run handles SSL termination
.requiresChannel(channel -> channel.anyRequest().requiresSecure())
// CORS configuration
.cors(cors -> cors.configure(http))
// CSRF configuration (stateless API)
.csrf(csrf -> csrf.disable())
// Authorization rules
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/api/v1/**").authenticated()
.anyRequest().denyAll()
);
return http.build();
}
}
Conclusion
Building Java applications for Google Cloud Run provides:
- Serverless operation with automatic scaling
- Cost efficiency with pay-per-use pricing
- Zero infrastructure management
- Fast deployment and iteration cycles
- Integration with Google Cloud services
Key optimization strategies covered:
- Container optimization with Jib and efficient base images
- Cold start reduction through warmup and JVM tuning
- Resource management with appropriate memory and CPU limits
- Health checks for reliable deployment and scaling
- Monitoring and logging with Cloud Operations
- Database and cache integration for performance
- Security best practices for production deployment
This architecture enables building highly scalable, cost-effective Java applications that automatically scale based on demand while maintaining excellent performance and reliability.