When JAVA_OPTS is defined in a CF manifest using a YAML block scalar (>), CF may deliver the value with literal newlines, or the value may contain pipe characters (|) as part of javaagent options:
env:
JAVA_OPTS: >
-javaagent:$HOME/BOOT-INF/lib/jfr-exporter.jar=enableExecutorMBeans|disableMyFeature
-XX:+UseZGC
-XX:+AlwaysPreTouch
-XX:-ZUncommit
-XX:MaxDirectMemorySize=256m
The profile.d/00_java_opts.sh script uses sed to substitute $JAVA_OPTS, $HOME and $DEPS_DIR into each .opts file. The sed delimiter is |, and the replacement string is not sanitised, so the command fails when the value contains | or newlines:
[APP/PROC/WEB/0] OUT Invoking pre-start scripts.
[APP/PROC/WEB/0] ERR sed: -e expression #1, char 257: unterminated `s` command
[APP/PROC/WEB/0] ERR sed: -e expression #1, char 257: unterminated `s` command
[APP/PROC/WEB/0] ERR sed: -e expression #1, char 257: unterminated `s` command
[APP/PROC/WEB/0] ERR sed: -e expression #1, char 257: unterminated `s` command
The error repeats once per .opts file. Because the script has no set -e, execution continues — but all opts content is dropped and the JVM starts with an empty JAVA_OPTS: no memory limits, no agents.
Additionally, & and \ in JAVA_OPTS are interpreted as sed replacement metacharacters, causing silent data corruption instead of a visible error.
The ERR lines are only visible in CF application logs; there is no staging failure or app crash that directly points to this cause.
Impact
All JVM flags are silently dropped at startup:
- Buildpack-calculated memory settings (
-Xmx, -Xss, -XX:MaxMetaspaceSize) absent → OOM / GC thrashing
- Java agents not attached → no APM/tracing instrumentation
-Djava.security.properties missing → CloudFoundryContainerProvider not registered → CF system certificates not loaded into JVM truststore → TLS handshake failures: (certificate_unknown) No trusted certificate found
The last point is tricky to diagnose: the connection between the sed error lines at startup and TLS failures later at runtime is not obvious.
Suggestions
- Avoid
sed for variable substitution in the assembly script; use bash parameter expansion instead, which has no special-character restrictions
- Normalize
JAVA_OPTS to a single line before substitution to handle YAML block scalar newlines
- Consider adding
set -e to the script so that any failure causes a hard stop rather than continuing with an empty or corrupt JAVA_OPTS
When
JAVA_OPTSis defined in a CF manifest using a YAML block scalar (>), CF may deliver the value with literal newlines, or the value may contain pipe characters (|) as part of javaagent options:The
profile.d/00_java_opts.shscript usessedto substitute$JAVA_OPTS,$HOMEand$DEPS_DIRinto each.optsfile. Theseddelimiter is|, and the replacement string is not sanitised, so the command fails when the value contains|or newlines:The error repeats once per
.optsfile. Because the script has noset -e, execution continues — but all opts content is dropped and the JVM starts with an emptyJAVA_OPTS: no memory limits, no agents.Additionally,
&and\inJAVA_OPTSare interpreted as sed replacement metacharacters, causing silent data corruption instead of a visible error.The
ERRlines are only visible in CF application logs; there is no staging failure or app crash that directly points to this cause.Impact
All JVM flags are silently dropped at startup:
-Xmx,-Xss,-XX:MaxMetaspaceSize) absent → OOM / GC thrashing-Djava.security.propertiesmissing →CloudFoundryContainerProvidernot registered → CF system certificates not loaded into JVM truststore → TLS handshake failures:(certificate_unknown) No trusted certificate foundThe last point is tricky to diagnose: the connection between the
sederror lines at startup and TLS failures later at runtime is not obvious.Suggestions
sedfor variable substitution in the assembly script; use bash parameter expansion instead, which has no special-character restrictionsJAVA_OPTSto a single line before substitution to handle YAML block scalar newlinesset -eto the script so that any failure causes a hard stop rather than continuing with an empty or corruptJAVA_OPTS