57.8 Project 8: Java Spring Boot (Gradle) with MySQL and MongoDB
Why: the problem
Suppose we are at MakeMyTrip and the hotel team writes its bookings service in Java with Spring Boot, like most large Indian product companies and banks. A new developer needs the right JDK, Gradle, MySQL and MongoDB before writing one line, and the build often fails because of a different JDK version. Java images are also famous for being big and slow to start. With Docker we build the jar inside a gradle:8-jdk21 container, so nobody needs Java on the laptop. We then run it on a small eclipse-temurin:21-jre image as a non-root user, with memory limits that the JVM respects. We use Spring profiles to switch between MySQL (Spring Data JPA) and MongoDB (Spring Data MongoDB) with one environment variable. We also use the Spring Boot Actuator health endpoint for the Docker health check, and layered jars so code changes rebuild in seconds.
What: every term in plain English
- Spring Boot — A Java framework that builds a runnable jar with a web server inside.
- Gradle — A build tool for Java. ./gradlew bootJar makes the runnable jar.
- JDK and JRE — The JDK compiles code. The JRE only runs it. The runtime image needs only the JRE.
- Eclipse Temurin — Free, well-tested OpenJDK builds. eclipse-temurin:21-jre is a common base.
- Spring profile — A named set of settings. SPRING_PROFILES_ACTIVE=mysql loads application-mysql.yml.
- Spring Data JPA — Talks to SQL databases through Java interfaces called repositories.
- Actuator — Adds /actuator/health and other ops endpoints.
- MaxRAMPercentage — Tells the JVM to use a percentage of the container memory limit for the heap.
How: step by step
- Write the Spring Boot app. Entity, repository, controller.
- Write build.gradle and two profiles. mysql and mongo.
- Write a multi-stage Dockerfile. gradle build stage, temurin JRE runtime.
- Run with MySQL. compose.mysql.yaml
- Run with MongoDB. compose.mongo.yaml
- Tune memory and start time. mem_limit and JAVA_OPTS.
- Clean up. down -v.
Architecture
Folder layout
java-bookings/
src/main/java/com/makemytrip/bookings/
App.java
Booking.java
BookingStore.java
Stores.java
src/main/resources/
application.yml
application-mysql.yml
application-mongo.yml
build.gradle
settings.gradle
Dockerfile
.env
compose.mysql.yaml
compose.mongo.yaml
Example 1: The Spring Boot code and Gradle build
Goal. The Spring Boot code and Gradle build.
settings.gradle
rootProject.name = 'bookings'
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.5'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.makemytrip'
version = '1.0.0'
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
repositories { mavenCentral() }
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
runtimeOnly 'com.mysql:mysql-connector-j'
}
src/main/java/com/makemytrip/bookings/Booking.java
package com.makemytrip.bookings;
import jakarta.persistence.*;
// MySQL row. MySQL gives a number id (AUTO_INCREMENT).
@Entity
@Table(name = "bookings")
public class Booking {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String hotel;
private int nights;
public Booking() {}
public Booking(String hotel, int nights) { this.hotel = hotel; this.nights = nights; }
public Long getId() { return id; }
public String getHotel() { return hotel; }
public int getNights() { return nights; }
}
src/main/java/com/makemytrip/bookings/BookingDoc.java
package com.makemytrip.bookings;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
// MongoDB document. MongoDB gives a text ObjectId, so the id is a String.
@Document("bookings")
public class BookingDoc {
@Id
private String id;
private String hotel;
private int nights;
public BookingDoc() {}
public BookingDoc(String hotel, int nights) { this.hotel = hotel; this.nights = nights; }
public String getId() { return id; }
public String getHotel() { return hotel; }
public int getNights() { return nights; }
}
src/main/java/com/makemytrip/bookings/BookingIn.java
package com.makemytrip.bookings;
// What the client sends in POST /bookings.
public record BookingIn(String hotel, int nights) {}
src/main/java/com/makemytrip/bookings/BookingStore.java
package com.makemytrip.bookings;
import java.util.List;
// The controller only knows this interface. One profile gives one implementation.
public interface BookingStore {
List<?> findAll();
Object save(BookingIn in);
}
src/main/java/com/makemytrip/bookings/Stores.java
package com.makemytrip.bookings;
import java.util.List;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Component;
interface SqlRepo extends JpaRepository<Booking, Long> {}
interface MongoRepo extends MongoRepository<BookingDoc, String> {}
@Component
@Profile("mysql")
class SqlStore implements BookingStore {
private final SqlRepo repo;
SqlStore(SqlRepo repo) { this.repo = repo; }
public List<?> findAll() { return repo.findAll(); }
public Object save(BookingIn in) { return repo.save(new Booking(in.hotel(), in.nights())); }
}
@Component
@Profile("mongo")
class MongoStore implements BookingStore {
private final MongoRepo repo;
MongoStore(MongoRepo repo) { this.repo = repo; }
public List<?> findAll() { return repo.findAll(); }
public Object save(BookingIn in) { return repo.save(new BookingDoc(in.hotel(), in.nights())); }
}
src/main/java/com/makemytrip/bookings/App.java
package com.makemytrip.bookings;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
@SpringBootApplication
@RestController
public class App {
private final BookingStore store;
public App(BookingStore store) { this.store = store; }
public static void main(String[] args) { SpringApplication.run(App.class, args); }
@GetMapping("/bookings")
public List<?> list() { return store.findAll(); }
@PostMapping("/bookings")
@ResponseStatus(HttpStatus.CREATED)
public Object add(@RequestBody BookingIn b) {
if (b.hotel() == null || b.nights() == 0)
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "hotel and nights are required");
return store.save(b);
}
}
What each line does
Booking and BookingDoc— Two small classes. MySQL uses a number id (Long). MongoDB uses a text ObjectId (String). One class cannot have both, so each database gets its own class.List<?>— Java generics: a list of any type. The MySQL store returns Booking rows, the Mongo store returns BookingDoc documents.@Profile("mysql")— Spring creates this store only when the mysql profile is active. The other store is skipped.JpaRepository<Booking, Long> and MongoRepository<BookingDoc, String>— Spring writes the SQL and the Mongo queries for findAll and save for us. The second type is the id type.BookingStore— Our own interface so the controller does not care which database is behind it.toolchain 21— Gradle compiles with Java 21 even if the machine has another JDK.mysql-connector-j— The MySQL JDBC driver, needed only at run time.
Green output
$ docker run --rm -v "$PWD":/home/gradle/app -w /home/gradle/app gradle:8.10-jdk21 gradle wrapper
BUILD SUCCESSFUL in 6s
$ ls
build.gradle gradle gradlew gradlew.bat settings.gradle src
Expected result. The Gradle wrapper (gradlew) is created by a throwaway container, so the laptop needs no Java.
Example 2: Two profiles: MySQL and MongoDB
Goal. Two profiles: MySQL and MongoDB.
src/main/resources/application.yml
server:
port: 8080
management:
endpoints.web.exposure.include: health,info
endpoint.health.probes.enabled: true
spring:
application.name: bookings
src/main/resources/application-mysql.yml
spring:
datasource:
url: jdbc:mysql://db:3306/bookings
username: ${DB_USER}
password: ${DB_PASSWORD}
jpa:
hibernate.ddl-auto: update
open-in-view: false
autoconfigure.exclude:
- org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
- org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
- org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration
src/main/resources/application-mongo.yml
spring:
data.mongodb.uri: ${MONGO_URL}
autoconfigure.exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
- org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
What each line does
probes.enabled— Adds /actuator/health/liveness and /readiness, useful for Docker and Kubernetes.${DB_USER}— Spring reads the value from the environment of the container.ddl-auto: update— Creates the bookings table on first start. Fine for practice; use Flyway in real projects.autoconfigure.exclude— Turns off the database we are not using, so the app does not try to connect to it.SPRING_PROFILES_ACTIVE— Set in Compose to mysql or mongo to load the right file.
Green output
$ ls src/main/resources
application-mongo.yml application-mysql.yml application.yml
Expected result. One jar, two profiles. The database is picked by one environment variable.
Example 3: Multi-stage Dockerfile with layered jars
Goal. Multi-stage Dockerfile with layered jars.
Dockerfile
# Stage 1: build the jar
FROM gradle:8.10-jdk21 AS build
WORKDIR /home/gradle/app
COPY settings.gradle build.gradle ./
RUN gradle dependencies --no-daemon > /dev/null
COPY src ./src
RUN gradle bootJar --no-daemon -x test
RUN java -Djarmode=tools -jar build/libs/bookings-1.0.0.jar extract --layers --launcher --destination /out
# Stage 2: run on a JRE
FROM eclipse-temurin:21-jre
RUN useradd --system --uid 10001 spring
WORKDIR /app
COPY --from=build /out/dependencies/ ./
COPY --from=build /out/spring-boot-loader/ ./
COPY --from=build /out/snapshot-dependencies/ ./
COPY --from=build /out/application/ ./
USER spring
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError"
EXPOSE 8080
HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=5 \
CMD curl -fs http://localhost:8080/actuator/health/readiness || exit 1
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]
What each line does
gradle dependencies first— Downloads all libraries into a cached layer before the code is copied.-x test— Skips tests in the image build; run them in CI before.jarmode=tools extract --layers— Splits the jar into dependencies and application layers. A code change rebuilds only the small application layer.eclipse-temurin:21-jre— About 270 MB, much smaller than the 600 MB gradle JDK image.MaxRAMPercentage=75— Heap is 75 percent of the container memory limit.ExitOnOutOfMemoryError— The JVM exits on OOM so Docker restarts it, instead of hanging.exec java— exec makes Java PID 1 so it gets the stop signal.
Green output
$ docker build -t java-bookings:1.0 .
=> [build 6/7] RUN gradle bootJar --no-daemon -x test
=> [stage-1 7/8] COPY --from=build /out/application/ ./
=> naming to docker.io/library/java-bookings:1.0
$ docker image ls java-bookings
REPOSITORY TAG SIZE
java-bookings 1.0 318MB
$ touch src/main/java/com/makemytrip/bookings/App.java && docker build -t java-bookings:1.1 . 2>&1 | grep CACHED | wc -l
9
Expected result. The image is about 318 MB and a code-only change reuses 9 cached layers.
Example 4: Run with MySQL, then MongoDB, with memory limits
Goal. Run with MySQL, then MongoDB, with memory limits.
compose.mysql.yaml
services:
api:
build: .
image: java-bookings:1.0
ports: ["8080:8080"]
environment:
SPRING_PROFILES_ACTIVE: mysql
DB_USER: bookings
DB_PASSWORD: ${DB_PASSWORD}
mem_limit: 768m
depends_on:
db: { condition: service_healthy }
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
MYSQL_DATABASE: bookings
MYSQL_USER: bookings
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes: [db-data:/var/lib/mysql]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${ROOT_PASSWORD}"]
interval: 5s
retries: 20
volumes:
db-data:
compose.mongo.yaml
services:
api:
image: java-bookings:1.0
ports: ["8080:8080"]
environment:
SPRING_PROFILES_ACTIVE: mongo
MONGO_URL: mongodb://root:${MONGO_PASSWORD}@mongo:27017/mmt?authSource=admin
mem_limit: 768m
depends_on:
mongo: { condition: service_healthy }
mongo:
image: mongo:7
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
volumes: [mongo-data:/data/db]
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 5s
retries: 20
volumes:
mongo-data:
.env
ROOT_PASSWORD=change-me-root
DB_PASSWORD=change-me-app
MONGO_PASSWORD=change-me-mongo
What each line does
mem_limit: 768m— The container can use at most 768 MB. With 75 percent the heap is about 576 MB.SPRING_PROFILES_ACTIVE— Loads application-mysql.yml or application-mongo.yml.start-period=40s— Spring Boot needs some seconds to start; failures during this time do not count./mmt in MONGO_URL— The database name used by Spring Data MongoDB.
Green output
$ docker compose -f compose.mysql.yaml -p mmt-mysql up -d --wait
Container mmt-mysql-db-1 Healthy
Container mmt-mysql-api-1 Healthy
$ docker compose -p mmt-mysql logs api | grep Started
api-1 | Started App in 6.842 seconds (process running for 7.6)
$ curl -s -X POST localhost:8080/bookings -H 'Content-Type: application/json' -d '{"hotel":"Taj Lands End","nights":2}'
{"id":1,"hotel":"Taj Lands End","nights":2}
$ curl -s localhost:8080/actuator/health
{"status":"UP","groups":["liveness","readiness"]}
$ docker stats --no-stream --format '{{.Name}} {{.MemUsage}}' mmt-mysql-api-1
mmt-mysql-api-1 241MiB / 768MiB
Expected result. Spring Boot starts in about 7 seconds, stays inside the 768 MB limit, and saves bookings in MySQL. The Mongo file runs the same image.
Every command, step by step (plain docker and Compose)
-
Make folders
mkdir -p java-bookings/src/main/java/com/makemytrip/bookings java-bookings/src/main/resources && cd java-bookingsWhat it does: Creates the Java package folders. Add every file from the examples.
(no output) -
Build the image
docker build -t java-bookings:1.0 .What it does: Compiles with Gradle in stage 1 and copies the layers into the JRE image.
naming to docker.io/library/java-bookings:1.0 -
Check the user
docker run --rm --entrypoint id java-bookings:1.0What it does: The app runs as the spring user.
uid=10001(spring) gid=999(spring) -
Start the MySQL stack
docker compose -f compose.mysql.yaml -p mmt-mysql up -d --waitWhat it does: MySQL first, then Spring Boot.
Container mmt-mysql-api-1 Healthy -
Watch the start
docker compose -p mmt-mysql logs -f apiWhat it does: Shows Spring logs. Press Ctrl+C to stop following.
Started App in 6.842 seconds -
Add a booking
curl -s -X POST localhost:8080/bookings -H 'Content-Type: application/json' -d '{"hotel":"Taj Lands End","nights":2}'What it does: Saves a row in MySQL.
{"id":1,"hotel":"Taj Lands End","nights":2} -
See the table
docker compose -p mmt-mysql exec db mysql -ubookings -pchange-me-app bookings -e 'SELECT * FROM bookings'What it does: Hibernate created the table on start.
id hotel nights 1 Taj Lands End 2 -
Check health
curl -s localhost:8080/actuator/health/readinessWhat it does: The same URL Docker uses for its health check.
{"status":"UP"} -
Switch to Mongo
docker compose -p mmt-mysql down docker compose -f compose.mongo.yaml -p mmt-mongo up -d --waitWhat it does: Same image with the mongo profile.
Container mmt-mongo-api-1 Healthy -
Add a booking in Mongo
curl -s -X POST localhost:8080/bookings -H 'Content-Type: application/json' -d '{"hotel":"Leela Palace","nights":3}'What it does: Saves a document. The id is now a text ObjectId, because BookingDoc uses a String id.
{"id":"671b2f9e8c1d4a2b3c4d5e6f","hotel":"Leela Palace","nights":3} -
See memory
docker stats --no-stream mmt-mongo-api-1What it does: Proves the JVM respects the limit.
241MiB / 768MiB -
Clean up
docker compose -f compose.mongo.yaml -p mmt-mongo down -v docker compose -f compose.mysql.yaml -p mmt-mysql down -v docker image rm java-bookings:1.0What it does: Removes everything from this project.
Untagged: java-bookings:1.0
When it is red 1
Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago.
Cause. Spring started before MySQL was ready, or the URL host is not db.
Fix, step by step
- Use depends_on with condition: service_healthy.
- Check the datasource url is jdbc:mysql://db:3306/bookings.
- Run docker compose ps to see if db is healthy.
When it is red 2
Container mmt-mysql-api-1 exited with code 137 (OOMKilled)
Cause. The JVM used more memory than mem_limit.
Fix, step by step
- Keep -XX:MaxRAMPercentage=75 so the heap fits.
- Raise mem_limit to 1g if the app really needs it.
- Check with docker inspect --format '{{.State.OOMKilled}}'.
When it is red 3
Failed to configure a DataSource: 'url' attribute is not specified
Cause. The mongo profile is active but DataSource auto-config was not excluded, or no profile is set.
Fix, step by step
- Set SPRING_PROFILES_ACTIVE to mysql or mongo.
- Check the exclude list in application-mongo.yml.
When it is red 4
Unsupported class file major version 65
Cause. Code compiled with Java 21 but run on an older JRE.
Fix, step by step
- Use eclipse-temurin:21-jre in the runtime stage.
- Keep the Gradle toolchain at 21.
When it is red 5
container mmt-mysql-api-1 is unhealthy
Cause. The health check ran before Spring finished starting.
Fix, step by step
- Add --start-period=40s to HEALTHCHECK.
- Check the URL /actuator/health/readiness with curl inside the container.
Practice tasks
Easy Task 1: Build
Why: Start.
What to do: Build java-bookings:1.0 and check its size.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
docker build -t java-bookings:1.0 .
docker image ls java-bookingsEasy Task 2: MySQL
Why: Basics.
What to do: Start the MySQL stack and add a booking.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
docker compose -f compose.mysql.yaml -p mmt-mysql up -d --wait
curl -s -X POST localhost:8080/bookings -H 'Content-Type: application/json' -d '{"hotel":"ITC Grand","nights":1}'Easy Task 3: Mongo
Why: Profiles.
What to do: Run the Mongo stack with the same image.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
docker compose -f compose.mongo.yaml -p mmt-mongo up -d --wait
curl -s localhost:8080/bookingsMedium Task 4: Start time
Why: Logs.
What to do: Find the Started App line and note the seconds.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
docker compose -p mmt-mysql logs api | grep StartedMedium Task 5: Cache layers
Why: Speed.
What to do: Change only App.java and count CACHED steps.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
docker build -t java-bookings:1.1 . 2>&1 | grep -c CACHEDMedium Task 6: OOM
Why: Limits.
What to do: Set mem_limit to 200m and watch the exit code.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
docker compose -p mmt-mysql up -d
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' mmt-mysql-api-1Medium Task 7: Health
Why: Actuator.
What to do: Read liveness and readiness.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
curl -s localhost:8080/actuator/health/liveness
curl -s localhost:8080/actuator/health/readinessHard Task 8: Flyway
Why: Migrations.
What to do: Add Flyway and a V1 migration file.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
implementation 'org.flywaydb:flyway-mysql'
# src/main/resources/db/migration/V1__create_bookings.sql
CREATE TABLE bookings (id BIGINT AUTO_INCREMENT PRIMARY KEY, hotel VARCHAR(100), nights INT);Hard Task 9: Smaller JRE
Why: Size.
What to do: Try eclipse-temurin:21-jre-alpine and compare sizes.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
FROM eclipse-temurin:21-jre-alpine
RUN adduser -S -u 10001 spring
docker build -t java-bookings:alpine .
docker image ls java-bookingsEasy Task 10: Clean
Why: Habit.
What to do: Remove both stacks and images.
- Use a VM or laptop with Docker and Compose.
- Create the java-bookings folder with the files from this lesson.
- Run the steps and compare with the solution.
Solution
docker compose -f compose.mongo.yaml -p mmt-mongo down -v
docker compose -f compose.mysql.yaml -p mmt-mysql down -v
docker image rm java-bookings:1.0 java-bookings:1.1Homework: try at home
Replace ddl-auto: update with a Flyway migration V1__create_bookings.sql, run the MySQL stack, and prove that the table is created by Flyway (flyway_schema_history table exists).
Recap
- Build Java inside a Gradle container; run on a JRE image.
- Layered jars make code-only rebuilds fast.
- MaxRAMPercentage keeps the JVM inside mem_limit.
- Spring profiles switch MySQL and MongoDB with one variable.
- Actuator readiness is the right health check URL.
- Use start-period because Java takes seconds to start.
FAQ
Q. Maven or Gradle?
Both work the same way in Docker. Use maven:3.9-eclipse-temurin-21 and mvn package for Maven.
Q. Can I use Jib or Buildpacks instead of a Dockerfile?
Yes. ./gradlew jib or ./gradlew bootBuildImage build images without a Dockerfile.
Q. Why not ddl-auto in production?
It can change tables in surprising ways. Use Flyway or Liquibase migrations.
Q. How do I make start-up faster?
Use Class Data Sharing, Spring AOT, or a GraalVM native image (start in under 1 second).
Q. How do I see heap usage?
curl localhost:8080/actuator/metrics/jvm.memory.used after exposing metrics.
Q. Why does the health check use curl?
The temurin image is Ubuntu-based and has curl. On alpine use wget.
Interview questions
-
How do you containerise a Spring Boot app well?
Model answer: Multi-stage build, JRE runtime, layered jar, non-root user, MaxRAMPercentage, actuator health check, exec form.
-
Why did older JVMs get OOMKilled in containers?
Model answer: Before Java 10 the JVM read host memory, not the cgroup limit, and sized the heap too big.
-
How do you switch config between environments in Spring?
Model answer: Profiles plus environment variables; the image stays the same.
-
What is the difference between liveness and readiness?
Model answer: Liveness says the process is alive; readiness says it can serve traffic now (for example the database is reachable).
-
How do you speed up Java image builds in CI?
Model answer: Cache the dependency layer, use BuildKit cache mounts for Gradle, and use layered jars.
Quick quiz
Q1. Why build the jar inside Docker?
So nobody needs the JDK or Gradle installed and every build uses the same versions.
Q2. What does MaxRAMPercentage do?
Sets the Java heap as a percentage of the container memory limit.
Q3. How does one jar work with MySQL and MongoDB?
Spring profiles load different settings and turn off the unused database.
Q4. Why layered jars?
Dependencies change rarely, so their layer stays cached and code changes rebuild fast.
Q5. Why exec in the ENTRYPOINT?
So Java becomes PID 1 and stops cleanly on docker stop.
Samajla ka?
Revision card
Interview card