Kotlin Bicycle Hire Project
Combine validation and state changes in a small hire desk with an explicit refusal policy.
How it works
The desk stores a bounded capacity and one current hired count. A positive request succeeds only if it fits, and a rejected request must preserve the earlier state. This project is in-memory and single-threaded; the Mutex lesson handles a separate concurrent boundary. Returning Boolean keeps this small acceptance result simple.
Run this Kotlin example
These are local Kotlin/JVM programs. Install a JDK and the compiler using the official Kotlin compiler guide, or use an IDE with Kotlin support. Check java -version and kotlinc -version in a new terminal. The reviewed compiler is Kotlin 2.4.21 with a JVM 21 target, executed on Temurin JDK 25.0.5. Use JDK 21 or newer for this target. Android, Kotlin/JS and Kotlin/Native require separate platform projects and are not the tested environment here.
Save the complete source as hire-project.kt in its own learning folder. The package line gives this example a namespace, not a network address. Bhylu has no Kotlin browser runner; do not use the JavaScript or Python editor. Apache, PHP and MySQL are not required.
Compile with kotlinc hire-project.kt -jvm-target 21 -include-runtime -d lesson.jar, then run java -jar lesson.jar. Use a learning folder you own and keep generated JARs outside the website. These sources require no third-party application libraries.
Download the complete hire-project.kt · Download all source files with hash checks
For the optional collection, save download-examples.mjs in a new learning folder and run node download-examples.mjs. A local preview uses node download-examples.mjs http://127.0.0.1:8000/examples/kotlin/. It refuses an existing bhylu-kotlin-examples folder and installs or executes no Kotlin code. Compile one ordinary lesson at a time by filename.
Example
package bhylu.lessons.k_hire_project
class HireCounter(val capacity: Int) {
init { require(capacity >= 0) }
var hired = 0
private set
val free: Int get() = capacity - hired
fun hire(amount: Int): Boolean {
if (amount <= 0 || amount > free) return false
hired += amount
return true
}
}
fun main() {
val desk = HireCounter(5)
for (amount in listOf(2, 0, 4, 3)) {
println("${desk.hire(amount)} ${desk.hired}")
}
println("free=${desk.free}")
}
Expected result
true 2 false 2 false 2 true 5 free=0
The sequence contains both an invalid zero and a too-large request between valid operations. Neither refusal changes hired. The separate multi-file lab compiles this domain rule with tests, including a maximum-Int fixture.
A real multi-file lab
The source collection includes a checked hire counter, station report and independent tests. Inside the downloaded bhylu-kotlin-examples folder, run node setup-lab.mjs. It verifies the included source hashes and creates a new bike-lab folder; existing output is refused. Change to that folder. Compile the app with kotlinc src/Domain.kt src/App.kt -jvm-target 21 -include-runtime -d app.jar, then java -jar app.jar. Compile tests separately with kotlinc src/Domain.kt src/Tests.kt -jvm-target 21 -include-runtime -d tests.jar, then java -jar tests.jar. This lab uses only Kotlin/JDK libraries and does not need coroutines or a test-framework download.
lab-domain.kt
package bhylu.bikelab
class HireCounter(val capacity: Int) {
init { require(capacity >= 0) }
var hired = 0
private set
val free: Int get() = capacity - hired
fun hire(amount: Int): Boolean {
if (amount <= 0 || amount > free) return false
hired += amount
return true
}
}
data class HireRow(val id: String, val station: String, val minutes: Int)
fun stationTotals(rows: List<HireRow>): Map<String, Long> {
require(rows.size <= 1000)
val seen = mutableSetOf<String>()
val totals = mutableMapOf<String, Long>()
for (row in rows) {
require(row.id.isNotBlank() && row.station.isNotBlank() && row.minutes in 1..240)
require(seen.add(row.id)) { "Duplicate hire id" }
totals[row.station] = (totals[row.station] ?: 0L) + row.minutes.toLong()
}
return totals.toSortedMap()
}
lab-app.kt
package bhylu.bikelab
fun main() {
val desk = HireCounter(5)
for (amount in listOf(2, 0, 4, 3)) println("${desk.hire(amount)} ${desk.hired}")
println("free=${desk.free}")
println(stationTotals(listOf(HireRow("H-21", "Canal", 20), HireRow("H-22", "Hill", 35), HireRow("H-23", "Canal", 25))))
}
lab-tests.kt
package bhylu.bikelab
private fun mustReject(block: () -> Unit) {
try { block() } catch (expected: IllegalArgumentException) { return }
error("Invalid input accepted")
}
fun main() {
mustReject { HireCounter(-1) }
for (capacity in 0..12) {
val desk = HireCounter(capacity)
var expected = 0
for (amount in listOf(1, 2, 0, -1, 4, 1, 9, 1)) {
val accepted = amount > 0 && amount.toLong() + expected.toLong() <= capacity.toLong()
if (accepted) expected += amount
check(desk.hire(amount) == accepted)
check(desk.hired == expected && desk.free == capacity - expected)
}
}
val large = HireCounter(Int.MAX_VALUE)
check(large.hire(Int.MAX_VALUE - 1))
check(!large.hire(2) && large.hired == Int.MAX_VALUE - 1)
check(large.hire(1) && large.free == 0)
for (size in 0..20) {
val rows = List(size) { i -> HireRow("H-$i", if (i % 2 == 0) "Canal" else "Hill", i + 1) }
val expected = mutableMapOf<String, Long>()
for (row in rows) expected[row.station] = (expected[row.station] ?: 0L) + row.minutes
check(stationTotals(rows) == expected)
}
for (rows in listOf(
listOf(HireRow("", "Canal", 1)),
listOf(HireRow("H", " ", 1)),
listOf(HireRow("H", "Canal", 0)),
listOf(HireRow("H", "Canal", 241)),
listOf(HireRow("H", "Canal", 2), HireRow("H", "Hill", 3)),
List(1001) { HireRow("H-$it", "Canal", 1) }
)) mustReject { stationTotals(rows) }
println("Hire boundaries, independent sequence model and report fixtures passed")
}
For exact language and library contracts, consult the official Kotlin documentation. These fictional fixtures are learning programs, not a deployed hire service.