Irfan Bilir
← All projectsGame · Java, Vue · 2026

Blackjack

A multiplayer blackjack game where friends sit at a public or private table and play rounds against the dealer. The Spring Boot server deals the cards and enforces the rules, and every screen at the table updates over a WebSocket the moment anything changes.

Status
Ongoing
Stack
Java 21, Spring Boot, WebSocket, Vue 3, Vite, Caddy, Docker
A table mid-round, with the dealer showing a 5 and one face-down card, and the host Alice on her turn with 23 seconds left next to Bob's hand
The lobby with the player's name, options to create a public or private table, join by code, and a list of open tables
Public tables are listed in the lobby; private ones are joined by code.
A finished round from Bob's view, with the dealer's revealed hand at 20 and both players marked as losing
The server never sends the dealer's face-down card to clients before it is revealed.

Why I built it

It started as a simple card game assignment that I ended up enjoying more than I expected. I decided to revisit it in my free time, take it a few steps further, and turn it into a fully playable Blackjack game. Eventually, I thought it would be fun to make it multiplayer too, so I could play with friends. What started as a small assignment turned into a fun side project to experiment with and build upon.

How it works

Docker setup · Dockerfile
# -------- Build stage --------
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app

# Wrapper and build scripts first, so dependencies are cached between builds
COPY gradlew settings.gradle build.gradle ./
COPY gradle ./gradle
RUN ./gradlew --no-daemon dependencies > /dev/null

COPY src ./src
RUN ./gradlew --no-daemon bootJar -x test

# -------- Runtime stage --------
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app

RUN addgroup -S app && adduser -S app -G app
COPY --from=build /app/build/libs/*.jar app.jar
USER app

ENV JAVA_OPTS="-XX:MaxRAMPercentage=75"
EXPOSE 8080

ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]
Docker setup · docker-compose.yml
services:
  web:
    build:
      context: .
      dockerfile: web/Dockerfile
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    environment:
      SITE_ADDRESS: ${SITE_ADDRESS:-localhost}
    volumes:
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      server:
        condition: service_healthy
    restart: unless-stopped

  server:
    build: server
    environment:
      JAVA_OPTS: "-XX:MaxRAMPercentage=75"
      # Optional overrides of blackjack.* settings, e.g. for shorter test runs
      BLACKJACK_AWAY_GRACE: ${BLACKJACK_AWAY_GRACE:-2m}
      BLACKJACK_HOST_AWAY: ${BLACKJACK_HOST_AWAY:-30s}
    mem_limit: 512m
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 30s
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:
Next projectPokémon Squad Lab