apache/shenyu · critical · IllegalStateException
shenyu.jwt.secretKey is not configured. In a multi-instance…
Error message
shenyu.jwt.secretKey is not configured. In a multi-instance Admin cluster, each instance would generate a different key, causing token verification failures. Please explicitly configure 'shenyu.jwt.secretKey' in your configuration.
What it means
At Admin startup, JwtProperties.init() (@PostConstruct) fails fast when shenyu.jwt.secretKey is blank or still set to the built-in default. This guard exists because in a multi-instance Admin cluster a per-instance random key would cause token verification failures across nodes, so an explicit shared key is mandatory.
Solutions
- Set an explicit shenyu.jwt.secretKey (a strong random string) in application.yml, or via --shenyu.jwt.secretKey=... or the SHENYU_JWT_SECRETKEY env var, identically on every Admin instance
- Generate a key (e.g. openssl rand -base64 48) and distribute the same value to all cluster nodes
- If upgrading, expect prior JWT sessions to be invalidated and inform users they must re-login
Example fix
// before (application.yml)
shenyu:
jwt:
secretKey:
// after
shenyu:
jwt:
secretKey: ${SHENYU_JWT_SECRET_KEY:aLongRandomSharedSecretValue} Defensive patterns
Strategy: validation
Validate before calling
if (props.getSecretKey() == null || props.getSecretKey().isBlank() || "JWT_DEFAULT_SECRET_KEY".equals(props.getSecretKey())) {
throw new IllegalArgumentException("shenyu.jwt.secretKey must be explicitly configured before starting admin");
} Prevention
- Always set shenyu.jwt.secretKey explicitly in every environment's application.yml or via env var
- Use the same key value across all admin replicas; store it in a secret manager
- Fail fast in CI by asserting the config key exists in deployment manifests
When it happens
Trigger: Starting shenyu-admin with shenyu.jwt.secretKey unset, empty, or left at AdminConstants.JWT_DEFAULT_SECRET_KEY; the IllegalStateException is thrown from the @PostConstruct init() during Spring bean initialization, so the application context fails to start.
Common situations: Fresh deployments using the default application.yml without setting a secret; upgrading to a version where the JWT key was decoupled from the user password hash (existing keys/default values invalidated); Docker/Kubernetes deployments missing the JWT secret env var in one replica.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- clientConfig must config " + getClientName() + " properties
- grpc client must config the contextPath, ipAndPort
- zookeeper url is empty
- is malformed
- illegal param, serverLists configuration required if…
AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12).
Data as JSON: /api/errors/d0e2facbfcfc70a4.
Report an issue: GitHub.
Appendix: source
Thrown at shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/JwtProperties.java:44
import org.springframework.stereotype.Component;
/**
* Jwt Properties.
*/
@Component
@ConfigurationProperties(prefix = "shenyu.jwt")
public class JwtProperties {
private static final Logger LOG = LoggerFactory.getLogger(JwtProperties.class);
private Long expiredSeconds = AdminConstants.THE_ONE_DAY_MILLIS_TIME;
private String secretKey;
@PostConstruct
private void init() {
if (StringUtils.isBlank(secretKey) || AdminConstants.JWT_DEFAULT_SECRET_KEY.equals(this.secretKey)) {
throw new IllegalStateException("shenyu.jwt.secretKey is not configured. "
+ "In a multi-instance Admin cluster, each instance would generate a different key, "
+ "causing token verification failures. "
+ "Please explicitly configure 'shenyu.jwt.secretKey' in your configuration.");
}
LOG.warn("JWT signing key is now decoupled from user password hash. "
+ "Existing sessions from previous versions will be invalidated and users will need to re-login. "
+ "If rolling back, all tokens issued by this version will also become invalid.");
}
/**
* Gets the value of expiredSeconds.
*
* @return the value of expiredSeconds
*/
public Long getExpiredSeconds() {
return expiredSeconds;
}
View on GitHub (pinned to 567142e072)