Deploying a Spring Boot App
What is Spring Boot?
Spring Boot is a powerful, production-ready Java framework that simplifies the development and deployment of Spring-based applications. Built on top of the Spring Framework, Spring Boot eliminates much of the boilerplate configuration and provides opinionated defaults, allowing developers to focus on business logic rather than framework configuration.
Key features include:
- Embedded Tomcat, Jetty, or Undertow web servers
- Simplified dependency management with Spring Boot starters
- Auto-configuration for common scenarios
- Externalized configuration through properties files and environment variables
- Comprehensive REST API development support
- Spring Data JPA for data persistence
- Spring Security for authentication and authorization
- Spring Actuator for monitoring and management endpoints
- Embedded database support (H2, Derby, HSQL)
- Easy integration with relational and NoSQL databases
- Caching support (Redis, Caffeine, Ehcache)
- Message queue integration (RabbitMQ, Kafka)
- Service discovery and circuit breaker patterns
- Comprehensive logging with SLF4J
- Build tool integration (Maven, Gradle)
- Docker containerization support
- Profiles for environment-specific configuration
- Testing utilities and frameworks
- Actuator endpoints for health checks, metrics, and tracing
Spring Boot is ideal for building RESTful APIs, microservices, enterprise applications, e-commerce platforms, content management systems, real-time applications, and large-scale web applications.
Prerequisites
Before deploying a Spring Boot application to Klutch.sh, ensure you have:
- Java 11 or higher installed on your local machine
- Maven 3.6+ or Gradle 6.0+ for build management
- Git and a GitHub account
- A Klutch.sh account with dashboard access
- Basic understanding of Java and Spring Framework concepts
- Optional: PostgreSQL or other database server for data persistence
Getting Started with Spring Boot
Step 1: Create a Spring Boot Project
Using Spring Initializr (https://start.spring.io), create a new project with the following settings:
- Project: Maven
- Language: Java
- Spring Boot Version: 3.2.0 or later
- Group: com.example
- Artifact: springboot-app
Add the following dependencies:
- Spring Web
- Spring Data JPA
- PostgreSQL Driver
- Spring Security
- Validation
- Actuator
Alternatively, create using command line:
curl https://start.spring.io/starter.zip \ -d dependencies=web,data-jpa,postgresql,security,validation,actuator \ -d language=java \ -d name=springboot-app \ -o springboot-app.zip
unzip springboot-app.zipcd springboot-appStep 2: Project Structure and Main Application Class
Your project structure should look like:
springboot-app/├── src/│ ├── main/│ │ ├── java/com/example/│ │ │ ├── SpringbootAppApplication.java│ │ │ ├── controller/│ │ │ ├── service/│ │ │ ├── repository/│ │ │ ├── model/│ │ │ ├── config/│ │ │ └── exception/│ │ └── resources/│ │ ├── application.properties│ │ └── application-prod.properties│ └── test/├── pom.xml└── README.mdCreate the main application class in src/main/java/com/example/SpringbootAppApplication.java:
package com.example;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication@ComponentScan(basePackages = "com.example")public class SpringbootAppApplication {
public static void main(String[] args) { SpringApplication.run(SpringbootAppApplication.class, args); }}Step 3: Create Entity Model
Create src/main/java/com/example/model/Item.java:
package com.example.model;
import jakarta.persistence.*;import jakarta.validation.constraints.*;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.time.LocalDateTime;
@Entity@Table(name = "items")@Data@NoArgsConstructor@AllArgsConstructorpublic class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@NotBlank(message = "Name is required") @Size(min = 1, max = 100, message = "Name must be between 1 and 100 characters") @Column(nullable = false, length = 100, unique = true) private String name;
@Size(max = 500, message = "Description must not exceed 500 characters") @Column(length = 500) private String description;
@NotNull(message = "Price is required") @Positive(message = "Price must be positive") @Column(nullable = false) private Integer price;
@Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt;
@Column(name = "updated_at") private LocalDateTime updatedAt;
@PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); }
@PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); }}Step 4: Create Repository Layer
Create src/main/java/com/example/repository/ItemRepository.java:
package com.example.repository;
import com.example.model.Item;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.stereotype.Repository;
@Repositorypublic interface ItemRepository extends JpaRepository<Item, Long> { Page<Item> findByNameContainingIgnoreCase(String name, Pageable pageable);}Step 5: Create REST Controller
Create src/main/java/com/example/controller/ItemController.java:
package com.example.controller;
import com.example.model.Item;import com.example.service.ItemService;import jakarta.validation.Valid;import lombok.RequiredArgsConstructor;import org.springframework.data.domain.Page;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Pageable;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;
import java.util.HashMap;import java.util.Map;
@RestController@RequestMapping("/api/items")@RequiredArgsConstructor@CrossOrigin(origins = "*", maxAge = 3600)public class ItemController {
private final ItemService itemService;
@GetMapping("/health") public ResponseEntity<Map<String, String>> health() { Map<String, String> response = new HashMap<>(); response.put("status", "healthy"); response.put("service", "spring-boot-app"); return ResponseEntity.ok(response); }
@GetMapping public ResponseEntity<Page<Item>> getAllItems( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String search) {
Pageable pageable = PageRequest.of(page, size); Page<Item> items = search != null && !search.isEmpty() ? itemService.searchItems(search, pageable) : itemService.getAllItems(pageable);
return ResponseEntity.ok(items); }
@GetMapping("/{id}") public ResponseEntity<Item> getItemById(@PathVariable Long id) { return itemService.getItemById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); }
@PostMapping public ResponseEntity<Item> createItem(@Valid @RequestBody Item item) { Item savedItem = itemService.saveItem(item); return ResponseEntity.status(HttpStatus.CREATED).body(savedItem); }
@PutMapping("/{id}") public ResponseEntity<Item> updateItem( @PathVariable Long id, @Valid @RequestBody Item itemDetails) {
return itemService.getItemById(id) .map(existingItem -> { existingItem.setName(itemDetails.getName()); existingItem.setDescription(itemDetails.getDescription()); existingItem.setPrice(itemDetails.getPrice()); Item updatedItem = itemService.saveItem(existingItem); return ResponseEntity.ok(updatedItem); }) .orElse(ResponseEntity.notFound().build()); }
@DeleteMapping("/{id}") public ResponseEntity<Void> deleteItem(@PathVariable Long id) { if (!itemService.getItemById(id).isPresent()) { return ResponseEntity.notFound().build(); } itemService.deleteItem(id); return ResponseEntity.noContent().build(); }
@GetMapping("/stats") public ResponseEntity<Map<String, Object>> getStats() { return ResponseEntity.ok(itemService.getStatistics()); }}Step 6: Create Service Layer
Create src/main/java/com/example/service/ItemService.java:
package com.example.service;
import com.example.model.Item;import com.example.repository.ItemRepository;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;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.HashMap;import java.util.Map;import java.util.Optional;
@Service@Transactional@RequiredArgsConstructor@Slf4jpublic class ItemService {
private final ItemRepository itemRepository;
public Page<Item> getAllItems(Pageable pageable) { log.info("Fetching all items with pagination"); return itemRepository.findAll(pageable); }
public Page<Item> searchItems(String query, Pageable pageable) { log.info("Searching items with query: {}", query); return itemRepository.findByNameContainingIgnoreCase(query, pageable); }
public Optional<Item> getItemById(Long id) { log.info("Fetching item with id: {}", id); return itemRepository.findById(id); }
public Item saveItem(Item item) { log.info("Saving item: {}", item.getName()); return itemRepository.save(item); }
public void deleteItem(Long id) { log.info("Deleting item with id: {}", id); itemRepository.deleteById(id); }
public Map<String, Object> getStatistics() { long totalItems = itemRepository.count(); Map<String, Object> stats = new HashMap<>(); stats.put("total_items", totalItems); stats.put("timestamp", System.currentTimeMillis()); return stats; }}Step 7: Configure Application Properties
Create src/main/resources/application.properties:
# Server Configurationserver.port=8080server.servlet.context-path=/
# Application Namespring.application.name=springboot-app
# Database Configurationspring.datasource.url=jdbc:postgresql://localhost:5432/springboot_dbspring.datasource.username=postgresspring.datasource.password=postgresspring.datasource.driver-class-name=org.postgresql.Driver
# JPA Configurationspring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialectspring.jpa.hibernate.ddl-auto=updatespring.jpa.show-sql=falsespring.jpa.properties.hibernate.format_sql=truespring.jpa.properties.hibernate.jdbc.batch_size=20spring.jpa.properties.hibernate.order_inserts=truespring.jpa.properties.hibernate.order_updates=true
# Logginglogging.level.root=INFOlogging.level.com.example=DEBUGlogging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
# Actuatormanagement.endpoints.web.exposure.include=health,info,metrics,prometheusmanagement.endpoint.health.show-details=alwaysmanagement.metrics.enable.jvm=trueCreate src/main/resources/application-prod.properties for production:
server.port=${PORT:8080}spring.datasource.url=${DATABASE_URL}spring.jpa.hibernate.ddl-auto=validatespring.jpa.show-sql=falselogging.level.root=WARNlogging.level.com.example=INFOStep 8: Build and Test Locally
# Build the applicationmvn clean package
# Run the applicationjava -jar target/springboot-app-*.jar
# Or use Maven directlymvn spring-boot:runAccess the API at http://localhost:8080/api/items/health
Deploying Without a Dockerfile
Klutch.sh uses Nixpacks to automatically detect and build your Spring Boot application from your source code.
Prepare Your Repository
- Initialize a Git repository and commit your code:
git initgit add .git commit -m "Initial Spring Boot app commit"- Create a
.gitignorefile:
target/*.class*.jar*.war*.ear*.db*.sqlite.classpath.project.settings/bin/build/.gradle/.idea/*.iml*.iws.DS_Store*.lognode_modules/-
Ensure
pom.xmlis in the root directory. Your Spring Boot application should be ready with:- Valid pom.xml with Spring Boot parent
- Source code in src/main/java
- Resources in src/main/resources
-
Push to GitHub:
git remote add origin https://github.com/YOUR_USERNAME/springboot-app.gitgit branch -M maingit push -u origin mainDeploy to Klutch.sh
-
Log in to Klutch.sh dashboard.
-
Click “Create a new project” and provide a project name.
-
Inside your project, click “Create a new app”.
-
Repository Configuration:
- Select your GitHub repository containing the Spring Boot app
- Select the branch to deploy (typically
main)
-
Traffic Settings:
- Select “HTTP” as the traffic type
-
Port Configuration:
- Set the internal port to 8080 (the default Spring Boot port)
-
Environment Variables: Set the following environment variables in the Klutch.sh dashboard:
DATABASE_URL: Your PostgreSQL connection string (e.g.,postgresql://user:password@postgres-host:5432/springboot_db)SPRING_PROFILES_ACTIVE: Set toprodfor production deploymentsJAVA_OPTS: Optional JVM options (e.g.,-Xmx512m -Xms256m)
-
Build and Start Commands (Optional): If you need to customize the build or start command, set these environment variables:
BUILD_COMMAND: Default runsmvn clean packageSTART_COMMAND: Default isjava -jar target/*.jar
-
Region, Compute, and Instances:
- Choose your desired region for optimal latency
- Select compute resources (Starter for prototypes, Pro/Premium for production)
- Set the number of instances (start with 1-2, scale as needed based on traffic)
-
Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.
-
Once deployment completes, your app will be accessible at
example-app.klutch.sh.
Verifying the Deployment
Test your deployed app:
curl https://example-app.klutch.sh/api/items/healthYou should receive:
{ "status": "healthy", "service": "spring-boot-app"}Access Swagger UI (if springdoc-openapi is added):
https://example-app.klutch.sh/swagger-ui.htmlDeploying With a Dockerfile
If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.
Create a Multi-Stage Dockerfile
Create a Dockerfile in your project root:
# Build stageFROM maven:3.9-eclipse-temurin-21 as builder
WORKDIR /app
# Copy pom.xml and download dependenciesCOPY pom.xml .RUN mvn dependency:go-offline -B
# Copy source code and buildCOPY src ./srcRUN mvn clean package -DskipTests -q
# Runtime stageFROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Install curl for health checksRUN apk add --no-cache curl
# Copy JAR from builderCOPY --from=builder /app/target/*.jar app.jar
# Create non-root user for securityRUN addgroup -g 1000 springboot && \ adduser -D -u 1000 -G springboot springboot && \ chown -R springboot:springboot /app
USER springboot
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -f http://localhost:8080/api/items/health || exit 1
# Expose portEXPOSE 8080
# Set JVM optionsENV JAVA_OPTS="-Xmx512m -Xms256m"
# Start the applicationCMD ["java", "-jar", "app.jar"]Deploy the Dockerfile Version
- Push your code with the Dockerfile to GitHub:
git add Dockerfilegit commit -m "Add Dockerfile for custom build"git push-
Log in to Klutch.sh dashboard.
-
- Select your GitHub repository and branch
- Set traffic type to “HTTP”
- Set the internal port to 8080
- Add environment variables (same as Nixpacks deployment)
- Click “Create”
-
Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.
Database Configuration
PostgreSQL Setup
PostgreSQL is the recommended database for Spring Boot applications. To use a PostgreSQL instance with Klutch.sh:
- Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
- Get the connection details from the PostgreSQL dashboard
- Set the DATABASE_URL environment variable:
postgresql://user:password@postgres-host:5432/springboot_db
Update your application-prod.properties:
spring.datasource.url=${DATABASE_URL}spring.jpa.hibernate.ddl-auto=validateDatabase Migrations with Flyway
Add Flyway dependency to pom.xml:
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId></dependency>Create migration files in src/main/resources/db/migration/V1__Initial_schema.sql:
CREATE TABLE items ( id BIGSERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, description VARCHAR(500), price INTEGER NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);
CREATE INDEX idx_items_name ON items(name);Security Configuration
Spring Security Setup
Add to pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>Create src/main/java/com/example/config/SecurityConfig.java:
package com.example.config;
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;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.CorsConfigurationSource;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration@EnableWebSecuritypublic class SecurityConfig {
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/api/items/health", "/actuator/health").permitAll() .anyRequest().authenticated() ) .httpBasic() .and() .csrf().disable() .cors();
return http.build(); }
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }}Caching Configuration
Redis Caching
Add dependencies to pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId></dependency>Create src/main/java/com/example/config/CacheConfig.java:
package com.example.config;
import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Bean;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration@EnableCachingpublic class CacheConfig {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory);
StringRedisSerializer stringSerializer = new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setValueSerializer(stringSerializer);
return template; }}Use caching in service:
@Cacheable(value = "items", key = "'all'")public List<Item> getAllItems() { return itemRepository.findAll();}
@CacheEvict(value = "items", allEntries = true)public Item saveItem(Item item) { return itemRepository.save(item);}Monitoring with Actuator
Spring Boot Actuator provides production-ready features. Configure in application.properties:
management.endpoints.web.exposure.include=health,info,metrics,prometheus,envmanagement.endpoint.health.show-details=alwaysmanagement.metrics.enable.jvm=truemanagement.metrics.enable.process=truemanagement.metrics.enable.system=trueAccess metrics:
- Health:
https://example-app.klutch.sh/actuator/health - Metrics:
https://example-app.klutch.sh/actuator/metrics - Info:
https://example-app.klutch.sh/actuator/info
Environment Variables and Configuration
Essential Environment Variables
Configure these variables in the Klutch.sh dashboard:
| Variable | Description | Example |
|---|---|---|
DATABASE_URL | PostgreSQL connection string | postgresql://user:pass@host:5432/db |
SPRING_PROFILES_ACTIVE | Active Spring profile | prod |
JAVA_OPTS | JVM options | -Xmx512m -Xms256m |
PORT | Application port | 8080 |
Customization Environment Variables (Nixpacks)
For Nixpacks deployments:
| Variable | Purpose | Example |
|---|---|---|
BUILD_COMMAND | Build command | mvn clean package |
START_COMMAND | Start command | java -jar target/*.jar |
Persistent Storage for Logs and Data
Adding Persistent Volume
- In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
- Click “Add Volume”
- Set the mount path:
/app/logs(for application logs) or/app/data(for data files) - Set the size based on your needs (e.g., 10 GB for logs, 20 GB for data)
- Save and redeploy
Logging to Persistent Storage
Update application-prod.properties:
logging.file.name=/app/logs/springboot.loglogging.file.max-size=10MBlogging.file.max-history=10Custom Domains
To serve your Spring Boot application from a custom domain:
- In the Klutch.sh app dashboard, navigate to “Custom Domains”
- Click “Add Custom Domain”
- Enter your domain (e.g.,
api.example.com) - Follow the DNS configuration instructions provided
Example DNS configuration:
api.example.com CNAME example-app.klutch.shTroubleshooting
Issue 1: Database Connection Errors
Problem: “Connection refused” or “database not found” errors.
Solution:
- Verify DATABASE_URL is correctly configured
- Ensure PostgreSQL instance is running and accessible
- Check database user has proper permissions
- Test connection with psql client
- Verify firewall rules allow the connection
Issue 2: Out of Memory Errors
Problem: Application crashes with OutOfMemoryError.
Solution:
- Adjust JAVA_OPTS:
-Xmx1024m -Xms512m - Optimize database queries and add indexing
- Implement caching strategies
- Monitor memory usage in dashboard
- Scale to instances with more resources
Issue 3: Slow Application Startup
Problem: Application takes a long time to start.
Solution:
- Check for long-running initialization logic
- Lazy initialize expensive beans
- Verify database migrations are optimized
- Monitor startup logs for bottlenecks
- Use Spring Boot startup actuator endpoint
Issue 4: High Memory Usage
Problem: Application consumes excessive memory.
Solution:
- Reduce connection pool size
- Enable caching for frequently accessed data
- Implement pagination for large datasets
- Monitor memory metrics via actuator
- Check for memory leaks in application code
Issue 5: Request Timeout Issues
Problem: API requests timeout or respond slowly.
Solution:
- Add database query optimization
- Implement caching with Redis
- Use async processing for long operations
- Configure proper timeouts in application.properties
- Monitor response times via metrics
Best Practices for Production Deployment
-
Use Spring Profiles: Separate configurations for dev/prod
Terminal window java -jar app.jar --spring.profiles.active=prod -
Implement Logging: Use structured logging
logger.info("Processing request for item: {}", itemId); -
Database Connection Pooling: Configure HikariCP
spring.datasource.hikari.maximum-pool-size=20spring.datasource.hikari.minimum-idle=5 -
Enable Metrics: Use Spring Boot Actuator
management.endpoints.web.exposure.include=metrics,prometheus -
Input Validation: Validate all user inputs
@Valid @RequestBody Item item -
Error Handling: Implement global exception handling
@ExceptionHandler(ResourceNotFoundException.class)public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) { } -
Security: Enable HTTPS and authentication
server.ssl.enabled=true -
Graceful Shutdown: Configure shutdown timeout
server.shutdown=gracefulserver.shutdown-wait-time=30s -
Health Checks: Use actuator health endpoint
GET /actuator/health -
Performance Optimization: Enable compression
server.compression.enabled=trueserver.compression.min-response-size=1024
Resources
- Spring Boot Official Documentation
- Spring Boot Getting Started Guide
- Spring Boot Reference Documentation
- Building a REST Service with Spring
- Accessing Data with JPA
- Securing a Web Application
- Spring Boot Actuator Documentation
Conclusion
Deploying Spring Boot applications to Klutch.sh provides a robust, scalable platform for building and running enterprise-grade Java applications. Spring Boot’s comprehensive ecosystem and opinionated defaults make it ideal for rapid development and production deployment.
Key takeaways:
- Use Nixpacks for quick deployments with automatic Maven/Gradle detection
- Use Docker for complete control over dependencies and JVM options
- Configure Spring profiles for different environments
- Leverage Spring Data JPA for efficient database operations
- Use Spring Security for comprehensive authentication and authorization
- Implement proper logging and monitoring with Actuator
- Enable caching strategies for performance optimization
- Configure persistent storage for logs and data
- Monitor application health and metrics through Actuator endpoints
- Keep dependencies updated for security and performance
For additional help, refer to the Spring Boot documentation or Klutch.sh support resources.