A variable like JAVA_OPTS="-Xmx256m -Dfoo=bar" is one string holding what a human reads as several arguments. Whether the process receives one argument or several is decided by whoever expands the variable—and that decision changes when a command line moves between a shell script and a service unit, even when the text looks identical in both places.
In a shell script, an unquoted $JAVA_OPTS undergoes word splitting: the string breaks on whitespace, then filename expansion can also occur. For these flags the JVM receives the separate argv entries it needs. Quote it and the JVM receives a single argument with spaces inside it—one “option” no parser ever defined. The same visual line in a systemd unit answers to systemd’s tokenizer instead of a shell’s—and in the unit context discussed in an arch-general reply of mine, the two spellings diverged: the unbraced $JAVA_OPTS was subject to word splitting while braced ${JAVA_OPTS} was not. Two spellings a shell treats identically, two different argvs.
The JVM may reject a garbled option at startup or run with a different flag set. Inspect the argv the process actually received, not just the text in the unit file.
The fix is to stop delegating tokenization to implicit machinery. Where the launcher permits it, write the options out as explicit arguments in the command line rather than routing them through one flattened string. In shell, the container for “several arguments” is an array, expanded element-per-argument:
java_opts=(-Xmx256m -Dfoo=bar)
java "${java_opts[@]}" -jar app.jarEach array element becomes exactly one argv entry, including values that contain spaces.