Dev Environment & First Project
You know TypeScript. You know Go. Now you need Kotlin running on your machine with a real project structure — not an Android app, a backend/CLI Kotlin project. This module gets you from zero to a working Kotlin Gradle project with Docker infrastructure, mapping every tool to its npm/go equivalent as we go.
Install JDK 21 with SDKMAN
Section titled “Install JDK 21 with SDKMAN”JDK 21 is the current LTS (Long-Term Support) release. Kotlin compiles to JVM bytecode and runs on the JDK. The mental model:
| TypeScript | Go | Kotlin |
|---|---|---|
| Node.js runtime | Go runtime (compiled in) | JDK (JVM runtime) |
nvm manages versions | Go manages itself | sdkman manages versions |
| V8 engine | Go compiler | JVM (HotSpot) |
If you use nvm for Node.js versions, SDKMAN is the exact same idea for JDK
versions.
-
Install SDKMAN:
Terminal window curl -s "https://get.sdkman.io" | bashsource "$HOME/.sdkman/bin/sdkman-init.sh"sdk version -
Install Eclipse Temurin JDK 21 (the community-standard build):
Terminal window sdk list java # browse available distributionssdk install java 21.0.5-temjava -version # openjdk version "21.0.5" ...javac -version # javac 21.0.5 -
Set it as your default (like
nvm alias default):Terminal window sdk use java 21.0.5-tem # switch for this shell (like nvm use)sdk default java 21.0.5-tem # set the default version
SDKMAN sets JAVA_HOME for you. Verify:
echo $JAVA_HOMEwhich java# /home/youruser/.sdkman/candidates/java/current/bin/javaInstall Gradle
Section titled “Install Gradle”Gradle is the build tool for Kotlin projects. The mental model:
| npm (TypeScript) | go (Go) | Gradle (Kotlin) |
|---|---|---|
package.json | go.mod | build.gradle.kts |
package-lock.json | go.sum | gradle.lockfile (optional) |
node_modules/ | $GOPATH/pkg/mod/ | ~/.gradle/caches/ |
npm install | go mod download | ./gradlew build |
npm run build | go build | ./gradlew build |
npm run start | go run . | ./gradlew run |
npm test | go test ./... | ./gradlew test |
npx | go run pkg@version | ./gradlew (wrapper) |
sdk install gradle 8.12gradle --versionIDE setup
Section titled “IDE setup”IntelliJ is made by JetBrains — the same company that created Kotlin — so the Kotlin support is unmatched. The Community Edition is free and open source.
sdk install idea# Or download "Community Edition" from:# https://www.jetbrains.com/idea/download/Key settings to configure:
- Set JDK: File → Project Structure → Project → SDK → select JDK 21.
- Enable auto-import: Settings → Build → Gradle → “Auto-import”.
- Kotlin code style: Settings → Editor → Code Style → Kotlin (defaults are good).
Shortcuts you’ll use constantly:
| Action | macOS | Linux |
|---|---|---|
| Run current file | Ctrl+Shift+R | Ctrl+Shift+F10 |
| Navigate to class | Cmd+O | Ctrl+N |
| Navigate to file | Cmd+Shift+O | Ctrl+Shift+N |
| Find usages | Alt+F7 | Alt+F7 |
| Refactor/rename | Shift+F6 | Shift+F6 |
| Quick fix | Alt+Enter | Alt+Enter |
| Go to definition | Cmd+B | Ctrl+B |
If you’d rather stay in VS Code (maybe you’re coming from TypeScript and live there):
code --install-extension fwcd.kotlin# Also recommended:code --install-extension vscjava.vscode-gradlecode --install-extension mathiasfrohlich.KotlinYour first Kotlin Gradle project
Section titled “Your first Kotlin Gradle project”Initialize a Kotlin application project:
mkdir -p ~/kotlin-hello && cd ~/kotlin-hello
gradle init \ --type kotlin-application \ --dsl kotlin \ --project-name kotlin-hello \ --package com.example \ --no-split-project \ --java-version 21This generates the following structure:
Directorykotlin-hello/
Directoryapp/
- build.gradle.kts deps & build config (like package.json)
Directorysrc/
Directorymain/kotlin/com/example/
- App.kt entry point
Directorymain/resources/ config files
- …
Directorytest/kotlin/com/example/
- AppTest.kt
Directorygradle/
- libs.versions.toml version catalog
Directorywrapper/ wrapper JAR + properties
- …
- gradlew wrapper script
- gradlew.bat
- settings.gradle.kts like the workspace root
Project structure, three ways
Section titled “Project structure, three ways”The same “where does code live” question, answered in each ecosystem:
Directorymy-app/
- package.json
- package-lock.json
- tsconfig.json
Directorysrc/
- index.ts
Directorytest/
- index.test.ts
Directorynode_modules/
- …
Directorymy-app/
- go.mod
- go.sum
- main.go
Directoryinternal/
- handler.go
Directorycmd/server/
- main.go
Directorymy-app/
- build.gradle.kts deps + build config
- settings.gradle.kts project name, module includes
- gradle/libs.versions.toml version catalog
- gradlew wrapper script
Directorysrc/
Directorymain/kotlin/com/example/
- Main.kt
Directorymain/resources/ config files
- …
Directorytest/kotlin/com/example/
- MainTest.kt
Directory.gradle/ cache (gitignored, like node_modules)
- …
The entry point
Section titled “The entry point”console.log("Hello, TypeScript!");
// Run: npx ts-node src/index.tspackage main
import "fmt"
func main() { fmt.Println("Hello, Go!")}
// Run: go run .package com.example
fun main() { println("Hello, Kotlin!")}
// Run: ./gradlew runKey differences:
- Kotlin’s
main()is a top-level function — no class wrapper needed (unlike Java). println()is a top-level function, notSystem.out.println().- The package declaration maps to the directory structure (like Go, unlike TypeScript).
- Semicolons are optional and conventionally omitted (like Go).
Build and run
Section titled “Build and run”./gradlew build # compile + run tests./gradlew run # run the app./gradlew test # tests only./gradlew clean # remove build artifacts./gradlew build -x test # build, skip tests (like npm run build --ignore-scripts)A simpler single-module project
Section titled “A simpler single-module project”gradle init creates a multi-module structure by default. For learning, here’s a
minimal single-module project you can set up by hand:
rootProject.name = "kotlin-hello"plugins { kotlin("jvm") version "2.1.0" application}
group = "com.example"version = "1.0-SNAPSHOT"
repositories { mavenCentral()}
dependencies { testImplementation(kotlin("test"))}
tasks.test { useJUnitPlatform()}
application { mainClass.set("com.example.MainKt")}package com.example
fun main() { println("Hello from Kotlin!")
// String templates (like JS template literals) val name = "Kotlin Developer" println("Welcome, $name!")
// Multi-line strings (like JS backticks, Go raw strings) val message = """ |This is a multi-line string. |The | character marks the margin. |Kotlin trims the left margin with trimMargin(). """.trimMargin() println(message)}If you built the project by hand, generate the wrapper so ./gradlew works:
gradle wrapper --gradle-version 8.12./gradlew runUnderstanding build.gradle.kts
Section titled “Understanding build.gradle.kts”Here’s an annotated build.gradle.kts mapped to the package.json concepts you
already know:
// ===== PLUGINS =====// Like "devDependencies" that add build capabilitiesplugins { // Compiles .kt files to JVM bytecode (like "typescript" + tsconfig.json) kotlin("jvm") version "2.1.0" // Adds the `run` task and packaging (like "scripts": { "start": ... }) application}
// ===== PROJECT METADATA =====group = "com.example" // like an npm org scope: @example/my-appversion = "1.0-SNAPSHOT" // SNAPSHOT = development (not released)
// ===== REPOSITORIES =====// Where to download dependencies (npm has npmjs.com, JVM has Maven Central)repositories { mavenCentral()}
// ===== DEPENDENCIES =====dependencies { // Runtime dependency (like npm "dependencies"). Format: "group:artifact:version" implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") // Test dependency (like npm "devDependencies") testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")}
tasks.test { useJUnitPlatform() // use JUnit 5 as the test engine}
application { mainClass.set("com.example.MainKt") // the entry point}Dependency scopes compared
Section titled “Dependency scopes compared”| npm (package.json) | Go (go.mod) | Gradle | When available |
|---|---|---|---|
dependencies | require | implementation | Compile + runtime |
dependencies | require | api | Compile + runtime + exposed to consumers |
devDependencies | require (test) | testImplementation | Test only |
| — | — | runtimeOnly | Runtime only (not at compile time) |
| — | — | compileOnly | Compile only (not packaged) |
Dependency coordinates
Section titled “Dependency coordinates”In npm you install lodash; in Go you import github.com/user/repo. On the JVM,
dependencies have three parts:
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0│ │ │├── Group ID ├── Artifact ID └── Version (like an npm scope) (like a package name)Find them on Maven Central search — the npmjs.com of the JVM world.
Version catalogs
Section titled “Version catalogs”Modern Gradle projects centralize dependency versions in a TOML file (like a
shared package.json in a monorepo):
[versions]kotlin = "2.1.0"ktor = "3.0.3"junit = "5.11.3"
[libraries]kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin" }ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
[plugins]kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }Then in build.gradle.kts you reference them type-safely:
dependencies { implementation(libs.kotlinx.serialization.json) implementation(libs.ktor.server.core) testImplementation(libs.junit.jupiter)}settings.gradle.kts
Section titled “settings.gradle.kts”This is the project-root config. In a single-module project it’s one line; in a multi-module project it lists the modules — the equivalent of npm workspaces or a Go workspace:
// package.json (npm workspaces){ "workspaces": ["app", "core", "api-client"]}use ( ./app ./core ./api-client)rootProject.name = "my-platform"
include("app") // the main applicationinclude("core") // shared libraryinclude("api-client") // API client moduleGradle commands cheat sheet
Section titled “Gradle commands cheat sheet”| What you want | npm | go | Gradle |
|---|---|---|---|
| Install dependencies | npm install | go mod download | ./gradlew build (auto-downloads) |
| Run the app | npm start | go run . | ./gradlew run |
| Run tests | npm test | go test ./... | ./gradlew test |
| Build for production | npm run build | go build -o app | ./gradlew build |
| Clean build output | rm -rf dist/ | go clean | ./gradlew clean |
| Add a dependency | npm install lodash | go get github.com/... | edit build.gradle.kts, then ./gradlew build |
| Format code | prettier --write . | gofmt -w . | ./gradlew ktlintFormat (with plugin) |
| Lint code | eslint . | golangci-lint run | ./gradlew ktlintCheck (with plugin) |
| List tasks | npm run | — | ./gradlew tasks |
| Run with args | npm start -- --port 8080 | go run . --port 8080 | ./gradlew run --args="--port 8080" |
Some flags worth knowing:
./gradlew tasks # list available tasks./gradlew build --info # more output (like npm --verbose)./gradlew build -x test # skip a task./gradlew build --refresh-dependencies # like rm -rf node_modules && npm install./gradlew run --continuous # rebuild on change (like nodemon / air)./gradlew dependencies # dependency tree (like npm ls)Docker infrastructure setup
Section titled “Docker infrastructure setup”This course uses a shared Docker Compose stack for PostgreSQL, Redis, and Kafka.
-
Start all services:
Terminal window cd shared-infradocker compose up -d -
Check status:
Terminal window docker compose ps# kotlin-course-postgres running 0.0.0.0:5432->5432/tcp# kotlin-course-redis running 0.0.0.0:6379->6379/tcp# kotlin-course-kafka running 0.0.0.0:29092->29092/tcp# kotlin-course-kafka-ui running 0.0.0.0:8090->8080/tcp -
Verify each service:
Terminal window docker exec kotlin-course-postgres pg_isready -U devdocker exec kotlin-course-redis redis-cli ping # PONG# Kafka UI is available at http://localhost:8090
| Service | Host | Port | Credentials |
|---|---|---|---|
| PostgreSQL | localhost | 5432 | user dev, password dev, db kotlin_course |
| Redis | localhost | 6379 | no auth |
| Kafka | localhost | 29092 | no auth |
| Kafka UI | localhost | 8090 | no auth (web UI) |
Managing the stack:
docker compose stop # stop, keep datadocker compose down # remove containers, keep volumesdocker compose down -v # remove EVERYTHING, including datadocker compose logs -f postgres # tail a service's logsdocker compose restart redis # restart a single serviceDocker for TS/Go devs — what’s different
Section titled “Docker for TS/Go devs — what’s different”You already use Docker. Here’s the JVM-flavored mental model:
| Concept | Node.js / Go | JVM / Kotlin |
|---|---|---|
| Base image | node:21-alpine / golang:1.22 | eclipse-temurin:21-jre-alpine |
| Build step | npm run build / go build | ./gradlew build |
| Runtime size | ~150MB (Node) / ~10MB (Go) | ~200MB (JRE) or ~30MB (GraalVM native) |
| Hot reload | nodemon / air | ./gradlew run --continuous or Spring DevTools |
A production multi-stage Dockerfile for Kotlin:
# Build stageFROM eclipse-temurin:21-jdk-alpine AS buildWORKDIR /appCOPY gradle gradleCOPY gradlew build.gradle.kts settings.gradle.kts ./RUN ./gradlew dependencies --no-daemonCOPY src srcRUN ./gradlew build -x test --no-daemon
# Runtime stageFROM eclipse-temurin:21-jre-alpineWORKDIR /appCOPY --from=build /app/build/libs/*.jar app.jarEXPOSE 8080ENTRYPOINT ["java", "-jar", "app.jar"]Project structure conventions
Section titled “Project structure conventions”In TypeScript you import from file paths; in Go, by module path. In Kotlin/JVM you use reverse-domain packages that map to directories:
| TypeScript | Go | Kotlin |
|---|---|---|
import { foo } from './utils' | import "myapp/internal/utils" | import com.example.myapp.utils.foo |
| File-path based | Module-path based | Package based (maps to directories) |
src/utils/index.ts | internal/utils/utils.go | src/main/kotlin/com/example/myapp/utils/Foo.kt |
package com.example.myapp.models
data class User( val id: Long, val name: String, val email: String,)A typical backend service layout:
Directorymy-kotlin-service/
- build.gradle.kts
- settings.gradle.kts
- gradle/libs.versions.toml
Directorysrc/
Directorymain/kotlin/com/example/myservice/
- Application.kt entry point
Directoryconfig/
- …
Directoryroutes/ HTTP handlers (like controllers)
- …
Directorymodels/ data classes (like TS interfaces)
- …
Directoryservices/ business logic
- …
Directoryrepositories/ data access
- …
Directorymain/resources/
- application.conf config (like .env)
- logback.xml logging config
Directorytest/kotlin/com/example/myservice/
- …
- docker-compose.yml service-specific infrastructure
Kotlin Gradle projects organize code into source sets:
| Source set | Directory | Purpose | npm equivalent |
|---|---|---|---|
main | src/main/kotlin/ | Application code | src/ |
main resources | src/main/resources/ | Config, templates | config/, .env |
test | src/test/kotlin/ | Test code | __tests__/, *.test.ts |
test resources | src/test/resources/ | Test fixtures | __fixtures__/ |
Practice
Section titled “Practice”Put the toolchain to work — build a real, small project from scratch.
Quick reference card
Section titled “Quick reference card”# Development workflowsdk use java 21.0.5-tem # ensure the correct JDK./gradlew build # compile + test./gradlew run # run the app./gradlew test # tests only./gradlew tasks # list available tasks./gradlew dependencies # show the dependency tree
# Infrastructurecd shared-infradocker compose up -d # start Postgres, Redis, Kafkadocker compose ps # check statusdocker compose down -v # clean shutdown
# Project initgradle init --type kotlin-application --dsl kotlingradle wrapper --gradle-version 8.12