Spring Boot is Java's most popular framework for building production-grade applications. It powers everything from microservices at Fortune 500 companies to startup MVPs. The embedded Tomcat server, auto-configuration, and opinionated defaults make it the go-to choice for enterprise Java development.
Deploying a Spring Boot application to production typically requires configuring a JVM, tuning memory settings, setting up reverse proxies, and managing SSL certificates. With Out Plane, you can deploy your Spring Boot application in under a minute. This guide shows you exactly how.
What You'll Need
Before starting, make sure you have:
- Java 17+ installed on your machine
- Maven or Gradle as your build tool
- A GitHub account
- A Spring Boot application in a GitHub repository
Don't have Java installed? Download it from Adoptium:
- Windows: Download the MSI installer from adoptium.net. Verify installation with
java -versionin PowerShell. - macOS: Use
brew install --cask temurinor download the PKG installer from adoptium.net - Linux: Run
sudo apt install openjdk-17-jdk(Ubuntu/Debian) orsudo dnf install java-17-openjdk-devel(Fedora)
For Maven, install with:
- Windows: Download from maven.apache.org and add to PATH
- macOS: Use
brew install maven - Linux: Run
sudo apt install maven(Ubuntu/Debian)
Once Java and Maven are installed, create a project folder:
mkdir my-spring-app && cd my-spring-appIf you don't have a Spring Boot application yet, use our example below.
Quick Start: Sample Spring Boot Application
You can generate a new project with Spring Initializr or create the files manually. Here's a minimal Spring Boot application you can use.
Create pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.2</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>my-spring-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>my-spring-app</name>
<description>Spring Boot application deployed on Out Plane</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>Create the main application class at src/main/java/com/example/myspringapp/MySpringAppApplication.java:
package com.example.myspringapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MySpringAppApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringAppApplication.class, args);
}
}Create a REST controller at src/main/java/com/example/myspringapp/controller/AppController.java:
package com.example.myspringapp.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class AppController {
@GetMapping("/")
public Map<String, String> home() {
return Map.of(
"message", "Hello from Spring Boot!",
"status", "running"
);
}
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "healthy");
}
@GetMapping("/api/items")
public List<Map<String, Object>> getItems() {
return List.of(
Map.of("id", 1, "name", "Item One", "active", true),
Map.of("id", 2, "name", "Item Two", "active", true),
Map.of("id", 3, "name", "Item Three", "active", false)
);
}
}Create src/main/resources/application.properties:
server.port=8080
spring.application.name=my-spring-app
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=when-authorizedTest your application locally:
mvn spring-boot:runVisit http://localhost:8080 in your browser. You should see the JSON response.
Now create a Dockerfile in the project root. A multi-stage build keeps the final image small by separating the build environment from the runtime:
# Build stage
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# Runtime stage
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]This produces a production-ready image with only the JRE and your application JAR. The build stage downloads dependencies first for better Docker layer caching.
Push this code to a GitHub repository, and you're ready to deploy.
Deploy in 3 Steps
Step 1: Connect Your Repository
- Go to console.outplane.com
- Sign in with your GitHub account
- Select your Spring Boot repository from the list
Out Plane detects your application and prepares the build process.
Step 2: Configure Your Application
Configure the following settings in the create application form:
Build Method
Select how Out Plane should build your application:
- Dockerfile (Recommended): Uses the multi-stage Dockerfile above for optimal image size and full control over JVM settings.
- Buildpacks: Automatically detects Java and builds from
pom.xmlorbuild.gradle.
For Spring Boot applications, Dockerfile is the recommended approach. It gives you control over JVM memory settings and produces smaller, more predictable images.
Basic Settings
- Port: Set to
8080 - Branch: Select
mainor your preferred branch - Region: Choose the region closest to your users (default: Nuremberg)
Environment Variables (Optional)
If your application uses environment-specific configuration, add them here. For our simple example, no environment variables are required.
For more complex applications, add variables using the Add button or Raw Edit for bulk entry:
SPRING_PROFILES_ACTIVE=prod
JAVA_OPTS=-Xms256m -Xmx512mStep 3: Deploy
Click Deploy Application and watch the build process:
- Queued → Waiting for resources
- Building → Compiling your Spring Boot application, downloading dependencies
- Deploying → Starting your application with embedded Tomcat
- Ready → Your app is live
Once the status shows Ready, your application is live. You can find your application URL at the top of the deployment page. Click the URL to open your Spring Boot app in a new tab. SSL is automatically configured.
Production Best Practices
JVM Memory Settings
Java applications need proper memory configuration. Set JVM heap size based on your container's available memory:
JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseG1GCA common rule: set the max heap (-Xmx) to roughly 75% of your container's memory limit. The remaining 25% covers metaspace, thread stacks, and native memory.
Update your Dockerfile entrypoint to respect JAVA_OPTS:
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]Spring Profiles
Use Spring profiles to manage environment-specific configuration. Create src/main/resources/application-prod.properties:
server.port=8080
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
logging.level.root=WARN
logging.level.com.example=INFO
management.endpoints.web.exposure.include=health,info,metricsActivate the production profile via environment variable:
SPRING_PROFILES_ACTIVE=prodThis keeps development and production settings separated without code changes.
Actuator Health Checks
Spring Boot Actuator provides built-in health check endpoints. With spring-boot-starter-actuator in your dependencies, the /actuator/health endpoint is available automatically:
{
"status": "UP"
}Out Plane uses health endpoints to verify your application is ready to receive traffic. Actuator also exposes metrics at /actuator/metrics for monitoring JVM performance, HTTP request counts, and database connection pool status.
Graceful Shutdown
Enable graceful shutdown so in-flight requests complete before the application stops:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30sThis gives active requests 30 seconds to finish before forcing shutdown. This prevents data loss during deployments and scaling events.
Externalized Configuration
Keep sensitive data out of your codebase. Spring Boot reads environment variables automatically:
// application.properties
// spring.datasource.url=${DATABASE_URL}
// spring.datasource.username=${DB_USERNAME}
// spring.datasource.password=${DB_PASSWORD}
// Or use @Value in your code
@Value("${app.api-key:default-value}")
private String apiKey;Spring Boot maps environment variables to properties automatically. DATABASE_URL maps to database.url, and SPRING_DATASOURCE_URL maps to spring.datasource.url.
Connecting a Database
Most Spring Boot applications need a database. Out Plane provides managed PostgreSQL:
- Go to Databases in the sidebar
- Click Create Database
- Select PostgreSQL version and region
- Copy the connection URL
Add the connection URL as an environment variable:
DATABASE_URL=postgres://user:password@host:5432/databaseAdd Spring Data JPA and PostgreSQL dependencies to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>Configure the datasource in application-prod.properties:
spring.datasource.url=${DATABASE_URL}
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5Create an entity and repository:
@Entity
@Table(name = "items")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private boolean active;
// Getters and setters
}@Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
List<Item> findByActiveTrue();
}Spring Data JPA handles connection pooling via HikariCP automatically. No additional configuration is needed for production-grade database connections.
Custom Domain Setup
Replace the default .outplane.app URL with your own domain:
- Navigate to Domains
- Click Map Domain
- Enter your domain (e.g.,
api.yourdomain.com) - Add the DNS records shown to your domain registrar
SSL certificates are automatically provisioned once DNS propagates.
Monitoring Your Application
After deployment, monitor your Spring Boot application:
- Logs: View real-time application logs including Spring Boot startup logs and request traces
- Metrics: Track CPU, memory, and network usage
- HTTP Logs: Analyze incoming requests, response times, and status codes
Access these from the sidebar in your application dashboard.
Spring Boot Actuator provides additional insight into your application. With the Actuator dependency included, these endpoints are available:
/actuator/health— Application health status and dependency checks/actuator/metrics— JVM memory, garbage collection, HTTP request statistics/actuator/info— Application metadata and build information
Troubleshooting
Out of Memory / Java Heap Errors
Configure JVM heap size. Java applications default to using a percentage of the host's total memory, which may exceed container limits. Set explicit limits:
JAVA_OPTS=-Xms256m -Xmx512mCheck your application logs for java.lang.OutOfMemoryError messages.
Port Mismatch
Check the port configuration. Make sure you set the port to 8080 in the application settings. Verify your application.properties contains server.port=8080 and your Dockerfile has EXPOSE 8080.
Build Failures
Verify your pom.xml or build.gradle is in the repository root. Maven and Gradle builds require the build file at the project root. Run mvn clean package locally to confirm the build succeeds before deploying.
Common causes:
- Missing
spring-boot-maven-plugininpom.xml - Incompatible Java version between local and Docker image
- Test failures (use
-DskipTestsin the Dockerfile build stage)
Slow Startup
Optimize your Spring Boot startup time. Spring Boot 3.x applications with many auto-configurations can take 10-30 seconds to start. Reduce startup time by:
- Excluding unused auto-configurations:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) - Using lazy initialization:
spring.main.lazy-initialization=true - Minimizing classpath scanning
Database Connection Errors
Verify DATABASE_URL format. Ensure the connection string follows this format:
postgres://username:password@hostname:port/databaseFor Spring Boot, you may also use the JDBC format:
spring.datasource.url=jdbc:postgresql://hostname:port/database
spring.datasource.username=username
spring.datasource.password=passwordCheck that your database is in the same region as your application for lowest latency.
Next Steps
Your Spring Boot application is now deployed and running in production. Here's what to explore next:
- Scale your application: Adjust instance types and auto-scaling settings for higher traffic
- Set up CI/CD: Enable automatic deployments on every git push
- Add monitoring: Integrate with external monitoring tools via Out Plane's metrics export
- Configure caching: Add Redis with Spring Data Redis for session storage and response caching
Summary
Deploying a Spring Boot application to Out Plane takes three steps:
- Connect your GitHub repository
- Configure port (8080), build method (Dockerfile), and environment variables
- Deploy and get your live URL with automatic HTTPS
No server configuration, no manual SSL setup, no infrastructure management. Your embedded Tomcat server runs inside a containerized environment with automatic scaling.
Ready to deploy your Spring Boot application? Get started with Out Plane and receive $20 in free credit. Pay only for what you use with per-second billing.