17

I am writing a systemd unit file for a Java application and I'd like to control the version of Java used to start it up. My (simplified) service file is

[Service]
Type=simple
EnvironmentFile=%h/Documents/apps/app/app-%i/app.cfg
ExecStart=${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar %h/Documents/apps/app/app-%i/myapp.jar
SuccessExitStatus=143

When trying to start it up I get an error back

Apr 28 12:43:37 rombert systemd[1613]: [/home/robert/.config/systemd/user/app@.service:7] Executable path is not absolute, ignoring: ${JAVA_HOME}/bin/java ${JAVA_OPT
Apr 28 12:43:37 rombert systemd[1613]: app@1.0.0.service lacks both ExecStart= and ExecStop= setting. Refusing.

I know that JAVA_HOME is correctly set ; if I change the ExecStart line to start with /usr/bin/java and then add something like -DsomeOption=${JAVA_HOME} I can see it just fine.

The obvious workaround is to create a wrapper script but I feel that it defeats the point of using a service file.

How can I set JAVA_HOME for my Java application using a unit file?

  • Why does the wrapper script defeat the purpose of using a service file, exactly? You still get systemd's sequencing and dependency tracking, monitoring, etc. Basically, systemd trades away free-form programmability we had with SysVinit in favor of baked-in DTRT logic. When "the right thing" is something systemd doesn't do, you need to put that outside systemd, as in a shell script. – Warren Young Apr 28 '15 at 9:59
  • @WarrenYoung - because I suddenly start managing shell scripts again. In my case not managing a shell script is more useful than the other bits. – Robert Munteanu Apr 28 '15 at 10:06
  • I really don't see the problem. Do you spend your days worrying about all the executables you have to manage, too? :) – Warren Young Apr 28 '15 at 10:16
  • 3
    From systemd.service(5): "Note that the first argument (i.e. the program to execute) may not be a variable." That explains why ${JAVA_HOME} is not expanded at the beginning of the applications path, but is when used at some later point. – Wieland Apr 28 '15 at 10:24
  • @WarrenYoung - I prefer a single wrapper over the binary. I understand that it's not an issue to everyone, but it is for me :-) – Robert Munteanu Apr 28 '15 at 11:57
12

From the "Command lines" section in systemd.service(5):

Note that the first argument (i.e. the program to execute) may not be a variable.

I was going to suggest using the instance specifier %i (you can read more about it in systemd.unit(5)), but (now we're back in systemd.service(5)):

the first argument of the command line (i.e. the program to execute) may not include specifiers.

I think the best option at this point really is creating a shell script that wraps the execution of the java binary as suggested by Warren Young or you could ExecStart a shell directly like in the example for shell command lines in the "Command Lines" section of systemd.service(5) which has the following example:

ExecStart=/bin/sh -c 'dmesg | tac'

so you could do (untested):

ExecStart=/bin/sh -c '${JAVA_HOME}....'
2

Another similar option is to use /usr/bin/env:

ExecStart=/usr/bin/env "${JAVA_HOME}/bin/java" -jar ...

This way you can omit ' quotes around the whole command which is useful if you need to nest quoted stuff.

PS. As a side note, it is important to enclose variable names in {braces} in Systemd files or else they will not be recognized correctly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.