system.getproperties()返回jvm启动时加载的内部属性,只认-d参数且运行时put无效;system.getenv()直接读操作系统环境变量,是进程启动时的快照且map只读。

System.getProperties 返回的是 JVM 启动时加载的配置属性
它读的是 Java 自己维护的一套键值对,比如 java.version、user.home、file.separator 这类与运行环境强相关的内部状态。这些值在 JVM 初始化阶段就固定了,后续改系统环境变量也影响不到它们。
常见错误现象:System.getProperties().put("my.conf", "test") 看似写进去了,但其他地方读不到——因为很多框架(如 Spring)默认用 System.getProperty() 读,而这个方法只认启动时传入的 -D 参数或安全策略允许的内置项,运行时 put 的值不会被自动纳入“有效属性”范围。
- 启动时注入才真正生效:
java -Dapp.env=prod MyApp→System.getProperty("app.env")能拿到"prod" - 修改已有属性(如
user.dir)基本无效,JVM 内部会缓存并忽略 set 操作 - 部分属性受安全管理器限制,普通代码无法读取(如
sun.boot.library.path在某些 JDK 版本里会抛SecurityException)
System.getenv 是直接访问操作系统的环境变量
它调用的是底层 OS 的 getenv(),返回的是进程启动那一刻继承来的环境快照,和 JVM 属性完全隔离。比如你在 shell 里 export MY_SERVICE_URL=https://api.example.com,Java 里就能用 System.getenv("MY_SERVICE_URL") 拿到。
容易踩的坑:System.getenv() 在 Windows 上不区分大小写("PATH" 和 "path" 都能取到),但在 Linux/macOS 上严格区分;另外,如果用 IDE 启动 Java 程序,环境变量得在 IDE 的运行配置里显式设置,而不是只改终端里的 export。
立即学习“Java免费学习笔记(深入)”;
- 不能通过
System.getenv().put(...)修改系统环境变量——这个 map 是只读的,调用 put 会直接抛UnsupportedOperationException - 某些容器环境(如 Docker)可能清空了大部分环境变量,
System.getenv()返回的 map 会比本地少很多,别默认它一定有HOME或USER - 敏感信息(如密码)不该塞进环境变量再靠
System.getenv()读,容易被ps aux或容器检查暴露
什么时候该用 getProperties,什么时候该用 getenv
核心判断标准:这个值是“Java 运行上下文的一部分”,还是“外部系统给进程的上下文”。
- 用
System.getProperties():需要获取java.io.tmpdir、os.name、或者你明确用-D启动参数控制的行为(如日志级别、feature flag) - 用
System.getenv():对接外部服务地址(DATABASE_URL)、云平台元数据(KUBERNETES_SERVICE_HOST)、CI/CD 流水线标识(GITHUB_ACTIONS) - 两者都别碰:临时状态、用户输入、加密密钥——这些该走配置中心、命令行参数或专用凭证管理器
兼容性与 JDK 版本差异
JDK 9+ 对 System.getenv() 做了小幅收紧:如果安全管理器启用,且没有授予 RuntimePermission("getenv.*"),调用会失败;而 System.getProperties() 在 JDK 17 后进一步限制了可读属性列表(比如 sun.* 开头的几乎全被屏蔽)。
-
System.getenv("PATH")在 JDK 8 可用,在 JDK 21 依然可用——这个最常用组合没变 -
System.getProperty("sun.java.command")在 JDK 8 能读进程启动命令,在 JDK 17+ 默认返回null,除非加启动参数--add-opens java.base/java.lang=ALL-UNNAMED - 跨平台脚本里别假设
System.getenv("HOME")一定存在,Windows 下更可能是System.getenv("USERPROFILE")










