현대적인 언어 중 하나인 Kotlin은 JetBrains에서 만든 언어로, Java와 100% 호환되는 언어이다. 그렇기 때문에 Kotlin은 Android 개발에서도 사용되고 있으며, 서버 개발에서도 사용되고 있다. JVM 생태계 뿐만 아니라 다양한 곳에서 점유율을 높이고 싶은지 JetBrains는 KMP(Kotlin Multiplatform)를 통해 iOS, JS, 웹 어셈블리 등 다양한 플랫폼에서 Kotlin을 사용할 수 있도록 밀어주고 있다.
재밌는 점은 Kotlin 커뮤니티에서 잘 언급되지는 않지만 Kotlin은 Python이나 Ruby, JavaScript와 같이 스크립트 언어로도 사용할 수 있다. 그런데 언급하지 않는 이유가 있다.

바로 2025년 1월 19일 현재 기준으로 출시된지 7년 정도된 기술임에도 불구하고 아직까지 실험적인 기능으로 분류되어 있다. 그렇기 때문에 안정성이나 성능에 대한 보장이 없다. 하지만 우리는 이미 Kotlin Script를 잘 사용하고 있다.

Spring 서버 개발, 안드로이드 앱 개발 등을 해봤다면 높은 확률로 Gradle Kotlin DSL을 사용해봤을 것이다. Gradle Kotlin DSL은 Kotlin Script를 사용한다. 자세히보면 확장자가 .kts
로 이는 Kotlin Script를 의미한다. 빌드 스크립트로 널리 사용되고 있음에도 불구하고 실험적인 기능으로 분류하는 것이 황당하지만 이미 정착된 기술이라고 할 수는 있을 것 같다. 이번 글에서는 Gradle Kotlin DSL을 제외하고 Kotlin Script를 어떻게 활용할 수 있을지 알아보자.
Kotlin Script 개념
일단 Kotlin Script에 대해 아주 간단히 알아보자. Script가 붙은 것처럼 Kotlin Script는 Kotlin을 마치 스크립트 언어처럼 사용할 수 있게 해준다. 즉, 컴파일이나 패키징 없이 바로 실행할 수 있다.
// hello.kts
fun say() {
println("Hello, Kotlin!")
}
say()
위 코드를 쉘에서 실행하면 따로 빌드하지 않아도 Hello, Kotlin!
이 출력된다. 로컬에 Kotlin이 설치되어 있다면 다음과 같이 실행할 수 있다.
$ kotlinc -script hello.kts
Hello, Kotlin!
어디에 쓸 수 있을까?
간단하게 실행할 수 있다는 것은 좋은데 어디에 쓸 수 있을까? 그전에 써야할 필요가 있을까? 오히려 쓰지 말아야 할 이유가 더 많아보인다.
- 베타도 아니고 실험 단계
- 현재 JVM 환경에서만 가능
- 솔직히 대안이 많다
사실 써야할 이유는 전혀 없다고 볼 수 있다. 그럼에도 불구하고 이 글을 쓰는 이유는 두 가지로 재밌어 보이고 없어질 것 같지는 않기 때문이다. 이 글을 보는 여러분은 굳이 직접 설치해서 사용할 필요는 없다. 이 글을 보면서 재밌게 보고 가면 된다. 그럼 이번엔 어디에 쓸 수 있을지 알아보자.
- 빌드를 위한 스크립트
- 이미 Gradle Kotlin DSL에서 잘 사용되고 있음
- CLI 환경에서 스크립트 실행
- Kotlin 프로그램 런타임에서 스크립트 실행
빌드 스크립트를 제외하고 두 가지 사용 사례가 있을 수 있다. CLI 환경에서 스크립트를 실행할 때는 다른 스크립트 언어를 사용하거나 Shell Script를 사용할 수 있지만 Kotlin Script를 사용할 수도 있다. 또한 Kotlin 프로그램 런타임에서 스크립트를 실행할 때도 Kotlin Script를 사용할 수 있다.
CLI 환경에서 스크립트 실행
앞서 말한 것 처럼 다른 스크립트 언어나 Shell Script 대신 Kotlin Script를 사용할 수 있다. 그렇다면 Kotlin Script를 사용하면 어떤 이점이 있을까?
- Shell Script 보다는 쉬움
- 타입 안정성
- 외부 라이브러리 사용 가능
- JVM 생태계와의 호환성
- Kotlin 언어만 익숙한 경우
이중에서 가장 큰 이점이 될 수 있는 것은 Kotlin 언어만 익숙한 경우일 것이다. 다른 언어도 생태계가 충분히 큰 경우가 많고 타입 안정성 또한 보장해줄 수 있기 때문이다. 복잡하고 큰 연산의 경우 Kotlin Script를 사용하는 것이 좋을 수 있지만 보통 스크립트는 간단한 작업을 위해 사용되기 때문에 큰 이점이 되지는 못한다. 오히려 JVM 워밍업 시간이 필요하므로 간단한 스크립트라면 워밍업 시간이 0에 가까운 Python이나 Node.js에 비해 느리다.
그렇지만 어차피 로컬에서 돌리는 스크립트라면 그 약간의 시간 차이가 크게 중요하지는 않을 것이다. 만약 코틀린을 사용하는 조직이라면 다양한 언어로 툴을 만드는 것보다 언어를 통일하는게 비용이나 유지보수 측면에서 더 좋을 가능성이 있다.
설치 및 실행
SDKMAN을 이용하여 Kotlin을 설치할 수 있다. 설치는 다음과 같이 가능하다.
# Install sdkman
$ curl -s https://get.sdkman.io | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
# Install kotlin
$ sdk install kotlin
이후엔 다음과 같이 작성한 hello.kts
를 실행해볼 수 있다.
// hello.kts
println("Hello, Kotlin!")
$ kotlinc -script hello.kts
Hello, Kotlin!
다른 스크립트처럼 shebang을 사용하면 더 간단하게 실행할 수 있다.
#!/usr/bin/env kotlinc -script
println("Hello, Kotlin!")
$ chmod +x hello.kts
$ ./hello.kts
Hello, Kotlin!
KScript
kotlinc
는 기본 기능만 있기 때문에 다른 언어를 대체하기는 역부족하다는 평가를 받는다. 그래서 KScript라는 것이 오픈소스로 개발되었다. KScript는 kotlinc
에 비해 스크립트 캐싱, 의존성 관리, 바이너리 패키징 등 다양한 기능을 추가로 제공한다. 설치는 마찬가지로 SDKMAN을 통해 가능하다.
# Install kscript using sdkman
$ sdk install kscript
주의할 점으로 아직 Kotlin 2.0에선 실행이 안되기 때문에 1.9 버전대를 사용해야한다.
외부 라이브러리 사용
KScript를 이용하면 외부 라이브러리를 편하게 사용할 수 있다. 다음은 fuel
라이브러리를 사용한 예시이다.
노드의 package.json이나 파이썬의 requirements.txt 처럼 외부로 빼지 않고 스크립트 내부에 기술한다는 점이 좀 색다르긴 합니다.
// main.kts
@file:DependsOn("com.github.kittinunf.fuel:fuel:2.3.1")
import com.github.kittinunf.fuel.httpGet
val (request, response, result) =
"https://httpbin.org/get"
.httpGet()
.responseString()
println(result.get())
코드를 보면 DependsOn
을 통해 의존성을 설정할 수 있고 그 후에 스크립트를 실행하면 알아서 설치하고 실행하는 것을 볼 수 있다. Node.js의 package.json이나 Python의 requirements.txt처럼 외부로 빼지 않고 스크립트 내부에 기술한다는 점이 좀 색다르다. 실행 결과는 다음과 같다.
$ kscript main.kts
[kscript] Resolving com.github.kittinunf.fuel:fuel:2.3.1...{
"args": {},
"headers": {
"Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
"Host": "httpbin.org",
...
바이너리 패키징
KScript를 이용하면 바이너리 패키징을 할 수 있다. 이를 통해 Kotlin이나 KScript가 없더라도 Java가 설치된 환경이라면 실행할 수 있게 배포할 수 있다. 바이너리 패키징을 하기 위해선 Gradle이 필요하다.
$ kscript --package main.kts # Need Gradle
[kscript] Packaging script 'main' into standalone executable...
[kscript] Packaged script 'main' available at path:
[kscript] /Users/kciter/Library/Caches/kscript/package_07f1f85044b41284dee18d4f8c159650/build/libs/main
$ /Users/kciter/Library/Caches/kscript/package_07f1f85044b41284dee18d4f8c159650/build/libs
$ ./main # Need Java
{
"args": {},
"headers": {
"Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
"Host": "httpbin.org",
"User-Agent": "Java/17.0.3",
"X-Amzn-Trace-Id": "Root=1-6676a491-5935eb22586d7aa550e76b8d"
},
"origin": "1.225.3.207",
"url": "https://httpbin.org/get"
}
단순히 --package
라는 옵션 하나만 추가하면 알아서 바이너리로 만들어준다. 바이너리로 만들면 자바 실행 환경만 갖추면 되기 때문에 만약 내가 내부 툴을 조직에 배포하고 싶다면 거쳐야할 허들이 줄어든다고 볼 수 있다.
IntelliJ와 함께 사용하기
kscript --idea
를 사용하면 자동으로 IntelliJ에서 편집할 수 있게 구성해준다.

이를 통해 자동 완성이나 오류를 IDE에서 볼 수 있다.
args 사용
KScript를 이용하면 자동으로 args를 받아올 수 있다
println(args.joinToString(", "))
$ kotlinc --script args.kts hi hello bye
$ kscript args.kts hi hello bye
hi, hello, bye
별도 라이브러리를 사용하고나 구현하지 않아도되니 간단한 스크립트를 만들때 편리하게 이용할 수 있다.
사용 사례
뭔가 본격적으로 터미널 애플리케이션 을 만든다면 여러 오픈소스 라이브러리를 이용할 수 있다. 대표적으로 다음과 같은 라이브러리가 있다.
- Clikt : https://github.com/ajalt/clikt
- Mordant : https://github.com/ajalt/mordant
먼저 Clikt는 사용자가 전달하는 옵션이나 입력을 쉽게 처리할 수 있게 해준다.
// ...
class Hello : CliktCommand() {
val count: Int by option(help="Number of greetings").int().default(1)
val name: String by option(help="The person to greet").prompt("Your name")
override fun run() {
repeat(count) {
echo("Hello $name!")
}
}
}
Hello().main(args)
$ kscript clikt.kts --count 3
Your name: kciter
Hello kciter!
Hello kciter!
Hello kciter!
코드를 보시다시피 입력 타입이나 사용자 입력인지, 옵션으로 전달되는지 디폴트 값이 있는지를 정의할 수 있다. 이 외에도 다양한 기능을 제공한다.
다음으로 Mordant는 터미널에서 UI를 쉽게 그릴 수 있게 도와준다. DSL을 이용하므로 편리하다.
val table = table {
tableBorders = NONE
borderType = SQUARE_DOUBLE_SECTION_SEPARATOR
align = RIGHT
column(0) {
align = LEFT
style = magenta
}
column(3) {
style = magenta
}
header {
style = magenta
row("", "Projected Cost", "Actual Cost", "Difference")
}
...
$ kscript ./table.kts
│ Projected Cost │ Actual Cost │ Difference
══════════╪════════════════╧═════════════╪════════════
Food │ $400 $200 │ $200
──────────┼──────────────────────────────┼────────────
Data │ $100 $150 │ $-50
──────────┼──────────────────────────────┼────────────
Rent │ $800 $800 │ $0
──────────┼──────────────────────────────┼────────────
Candles │ $0 $3,600 │ $-3,600
──────────┼──────────────────────────────┼────────────
Utility │ $154 $150 │ $-5
══════════╪══════════════════════════════╧════════════
Subtotal │ $-3,455
백오피스를 개발한다면 CSV나 엑셀 파일을 자주 건들게 되는데, 이럴 때 데이터를 파싱해서 취합하거나 확인하는 용도로 사용할 수도 있다.
결론은?
일단 해볼 수 있는건 생각보다 많다.
- 배포 자동화
- 온보딩 툴
- 데이터 전처리
- 시드 데이터 추가
- ...
이외에도 요즘은 GPT와 연동하여 다양한걸 만들어 볼 수도 있을 것이다. 필자가 몇 가지 예제를 더 만들어 두었다 만약 궁금하다면 다음 링크를 살펴보자. https://github.com/kciter/kotlin-script-examples
런타임 환경에서 스크립트 실행
런타임 환경에서 Kotlin Script를 실행할 수 있는 방법은 Java Scripting API나 Embeddable Host를 사용하는 방법 두 가지가 있다. 그렇지만 사실 Java Scripting API가 내부적으론 Embeddable Host를 이용하기 때문에 같다고 볼 수 있다.
런타임 환경에서 스크립트를 실행할 수 있으면 유용할 것 같지만 문제는 자료가 거의 없다! 그나마 튜토리얼과 예제 저장소가 유용하다.
- https://kotlinlang.org/docs/custom-script-deps-tutorial.html
- https://github.com/Kotlin/kotlin-script-examples
Java Scripting API (JSR-223)
Java Scripting API는 스크립트 코드를 JVM 위에 동작하는 프로그램에서 실행할 수 있게 해준다. 인터페이스 API기 때문에 구현체만 있다면 어떠한 스크립트 언어라도 실행이 가능하다. 물론 Kotlin Script 구현체가 있기 때문에 Kotlin Script도 실행할 수 있다. 이를 위해 다음과 같이 의존성을 추가해야 한다.
org.jetbrains.kotlin:kotlin-scripting-jsr223
의존성 필수- 라이브러리 이용이나 다른 스크립트 불러오기는 직접 구현해야함
kotlin-main-kts
를 사용하는 경우 extension을main.kts
로 지정하면 알아서 다 해준다org.jetbrains.kotlin:kotlin-main-kts
추가 필요- 의존성 관리, 다른 스크립트 불러오기 등 필요한 것들을 미리 다 구현해둠
이후에 스크립트 실행은 다음과 같이 코드를 작성하여 실행할 수 있다.
import javax.script.ScriptEngineManager
fun main() {
val engine = ScriptEngineManager().getEngineByExtension("main.kts")!!
engine.eval(
"""
val a = 2
val b = 3
println("a + b = ${'$'}{a + b}")
""".trimIndent() // Output: a + b = 5
)
}
코드가 생각보다 간단하다. 단순히 라이브러리 의존성만 추가해도 별도 설정없이 바로 사용이 가능하다. 물론 kotlin-main-kts
라이브러리까지 추가해서 총 2개 의존성을 추가해야 알아서 다 해준다고 볼 수 있다.
스크립트 시작 전 미리 값을 넣어두는 것도 가능하다. 키-벨류로 값을 넣어주면 스크립트 실행시 변수로 사용 가능하다.
import javax.script.ScriptEngineManager
fun main() {
val engine = ScriptEngineManager().getEngineByExtension("main.kts")!!
engine.put("a", 2) engine.put("b", 3) engine.eval(
"""
println("a + b = ${'$'}{a + b}") """.trimIndent()
)
}
Embeddable Host
Embeddable Host는 Kotlin에서 직접 제공하는 내장 컴파일러를 이용하는 방식이다. 앞서 Java Scripting API도 사실 이걸 이용하고 있다고 말한 것처럼 결국 내부는 다 같다. 그런데 왜 굳이 둘로 분리하고 공식 홈페이지는 이 방식만 설명하는지는 잘 모르겠지만 추측해보면 추후 멀티플랫폼을 위함이 아닐까라고 생각한다.
해당 방식은 구현하기 위해 Script Definition과 Scripting Host를 만들어야 한다. 코드가 좀 길어져서 이 글에서는 생략하고 넘어가겠다. 만약 궁금하다면 다음 공식 홈페이지 문서를 읽어보기를 바란다. https://kotlinlang.org/docs/custom-script-deps-tutorial.html
무엇을 할 수 있을까?
안타깝지만 Kotlin Script는 실험 단계이므로 프로덕션 환경에서 사용하기에는 무리가 있다. 그래도 몇 가지 실험적인 것을 해보는 것은 재미있을 수 있다. 다음으로 필자가 생각한 사용 사례 예시 두 가지를 살펴보자.
HTML Template
kotlinx.html
라이브러리를 사용하면 HTML을 Kotlin DSL로 작성 가능하다. ktor에선 이를 이용하여 템플릿을 만든다. 물론 결국 Kotlin DSL이므로 변경할 떄마다 다시 빌드를 해야한다는 단점이 있다. Auto Reload가 있긴하지만 그래도 약간의 시간이 필요하다.
그런데 만약 Kotlin Script에서 해당 라이브러리를 사용한다면 별도 컴파일 타임 없이 템플릿 작성이 가능하지 않을까? 그런 마음으로 다음과 같이 코드를 작성해보았다.
import javax.script.ScriptEngineManager
data class Params(val name: String)
fun main() {
val engine = ScriptEngineManager().getEngineByExtension("main.kts")!!
engine.put("params", Params("Kotlin"))
engine.eval("""
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.0")
import kotlinx.html.*; import kotlinx.html.stream.*; import kotlinx.html.attributes.*
createHTML().html {
body {
h1 { +"Hello, ${"$"}{params.name}!" }
}
}""".trimIndent()
)
}
일단은 테스트했을 때 잘돌아간다. 만약 이를 외부 파일로 분리한다면 빌드 없이도 바로 반영할 수 있을 것이다. 그리고 HTML이 아니라 JSON, XML 등 다양한 포맷로도 사용할 수 있을 것이다. 이 정도면 Kotlin Script가 Stable 상태가 됐을 때 고도화 시키는 것도 괜찮지 않을까?
Ruby Warrior
이전에 유명했던 오픈소스 게임으로 Ruby Warrior라는 것이 있다. Ruby Warrior는 게임을 하는 사람이 Ruby 코드를 넣어 캐릭터를 조작하고 퍼즐을 클리어하는 게임이다.

Ruby나 Python, Node.js같은 여러 언어에서 구현이 됐지만 스크립트가 아닌 언어는 구현체가 거의 없다. 왜냐하면 스크립트 언어는 eval이 가능하니 구현이 편리하지만 컴파일을 하는 언어는 별도로 컴파일하고 결과를 출력하는 복잡한 과정이 필요하다. 하지만 Kotlin은 Kotlin Script가 있으므로 상대적으로 쉽게 구현이 가능할 것이다.
이처럼 런타임 중 사용자에게 직접 코드를 받아 실행하는 경우 Kotlin Script를 사용하면 편리할 것이다. 물론 이런 경우는 거의 없고 보안 문제가 있을 수 있으니 주의할 필요는 있다.
필자는 이를 구현해보려 했으나 시간이 부족하여 아직 완성하지 못했다. 빠른 시일 내에 완성해서 공개해보려고 한다.
마치며
결론적으로 실험 단계지만 쓰려면 잘 쓸 수 있을 것 같다는 생각이 들었다. 하지만 여러모로 아직 부족하다. 특별하게 우위에 있는 부분이 없기 때문에 다른 스크립트 언어를 대체할 수 있냐고하면 확실하게 그렇다고는 말할 수 없다.
그럼에도 불구하고 Spring 서버 팀이나 안드로이드 팀 등 Kotlin을 주력으로 쓰는 팀이라면 이용해 볼만한 가치는 있다고 생각한다. 다만 Embedded Scripting은 실제 제품에 이용하기엔 위험하므로 사용하지 않는 것이 좋다. 추후 빌드 시간이 너무 오래걸리는 경우 스크립트로 외부화하는 경우도 있을수는 있겠지만 아직은 좀 먼 이야기 같다.
그래서 결론은 팀 내부에서 쓰는 스크립트를 만들 때 이용해보면 어떨까? 정도로 생각하면 좋을 것 같다.