Lanky Dan Blog

Running a Kotlin class as a subprocess

May 25, 2019

kotlin

Last week I wrote a post on running a Java class as a subprocess. That post was triggered by my need to run a class from within a test without prebuilding a Jar. The only difference between what I wrote in that post and what actually happened was the language. I used Kotlin to write that test. Not Java. Therefore, I have decided to write this follow up post that builds upon what I previously wrote and focuses on starting a Kotlin subprocess instead of a Java subprocess.

Let’s start with an equivalent implementation of the exec from Running a Java class as a subprocess:

@Throws(IOException::class, InterruptedException::class)
fun exec(clazz: Class<*>, args: List<String> = emptyList(), jvmArgs: List<String> = emptyList()): Int {
  val javaHome = System.getProperty("java.home")
  val javaBin = javaHome + File.separator + "bin" + File.separator + "java"
  val classpath = System.getProperty("java.class.path")
  val className = clazz.name

  val command = ArrayList<String>()
  command.add(javaBin)
  command.addAll(jvmArgs)
  command.add("-cp")
  command.add(classpath)
  command.add(className)
  command.addAll(args)

  val builder = ProcessBuilder(command)
  val process = builder.inheritIO().start()
  process.waitFor()
  return process.exitValue()
}

Short explanation since I covered everything else in my previous post.

The path to the Java executable is retrieved and stored in javaBin. Using this along with the passed in clazz, args, jvmArgs and the process’s classpath a command is created and executed by the ProcessBuilder. The class is now successfully running as a subprocess.

The code above is pretty much a copy and paste of the Java implementation. The differences are the order of the function parameters. I decided to switch args and jvmArgs around so I can make full use of their default values. This is based on the assumption that you are more likely to provide args instead of jvmArgs. It isn’t really that much of a concern when running from Kotlin since you can name the parameters when calling the function. But, if any Java code needs to call this function then the order of the parameters might be helpful (as well as adding on @JvmOverloads).

Below are some ways of calling exec:

exec(MyProcess::class.java, listOf("3"), listOf("-Xmx200m"))
// jvmArgs = emptyList()
exec(MyProcess::class.java, listOf("3"))
// args = emptyList()
exec(MyProcess::class.java, jvmArgs = listOf("-Xmx200m"))
// both args and jvmArgs = emptyList()
exec(MyProcess::class.java)

A noticeable difference when writing in Kotlin instead of Java is the number of ways that you can define a main function. This is due to Kotlin providing many different routes to create a static function.

The function I provided above will run a main function in the two following scenarios:

Inside a companion object and annotated with @JvmStatic

class MyProcess {
  companion object {
    @JvmStatic
    fun main(args: Array<String>) {
      // do stuff
    }
  }
}

Inside an object and annotated with @JvmStatic

object MyProcess {
  @JvmStatic
  fun main(args: Array<String>) {
    // do stuff
  }
}

Another way to create a static function in Kotlin is to define it outside of a class:

@file:JvmName("MyProcess")

package dev.lankydan

fun main(args: Array<String>) {
  // do stuff
}

A name needs to be provided so Java knows what to do with it. Executing the main function in this snippet is one situation where this is needed. Unfortunately, the exec function won’t work in this situation, since the MyProcess class doesn’t actually exist. Preventing it from compiling. Funnily enough, calling MyProcess from Java code will compile without any changes.

To resolve the compilation error found in the Kotlin version, exec’s parameters need to change very slightly:

@Throws(IOException::class, InterruptedException::class)
fun exec(className: String, args: List<String> = emptyList(), jvmArgs: List<String> = emptyList()): Int {
  // className passed into command
}

This change sidesteps the compilation error the original exec function presents. But, this comes with the downside of not really being type-safe and might lead to a few errors here and there where an invalid string is passed in. As you can see below:

exec("dev.lankydan.MyProcess", listOf("argument"), listOf("-Xmx200m"))

Something about this change really rustles my jimmies. Therefore providing both overloads is probably the sanest thing to do.

To wrap up, although most of what I have to say about executing Kotlin classes as subprocesses was already covered in Running a Java class as a subprocess. There are a few differences due to Kotlin’s flexibility. A main function in Kotlin can be defined in multiple ways and unfortunately, one of one those does not get along with exec function that the rest are happy with. Luckily the solution is a small code change that can work well if there are overloads for both String and Class class names.

If you enjoyed this post or found it helpful (or both) then please feel free to follow me on Twitter at @LankyDanDev and remember to share with anyone else who might find this useful!


Dan Newton

Augmenting a Spring Data repository through delegation

September 14, 2019
springspring datakotlinjavar2dbcspring data r2dbcreactivereactive streamsspring boot

I have recently written several posts about Kotlin’s delegation. In doing so, I realised a useful way to apply it to Spring Data…

Implementing multiple interfaces through delegation

September 05, 2019
kotlin

In Kotlin, a class can implement multiple interfaces. This is common knowledge. A class can also use delegation to implement numerous…

Streaming live updates from a reactive Spring Data repository

August 29, 2019
springspring datakotlinjavar2dbcspring data r2dbcreactivereactive streams

This post details a naive implementation of streaming updates from a database to any other components that are interested in that data…

The potential traps in Kotlin's Data Classes

August 17, 2019
kotlin

The aim of this post is not to point out some massive flaws in Kotlin’s design of data classes and show you how to get passed them. Actually…

Connecting a Ktor web server to a Corda node

August 12, 2019
cordakotlindltdistributed ledger technologyblockchain

The preparation for this blog post began several weeks ago (probably over a month by now). Before I could write about melding Corda and Ktor…