Skip to content

Ensured *Builder instances are no longer created with reflection #989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
## Release Notes

### 0.12.7

This patch release:

* Improves performance slightly by ensuring all `jjwt-api` utility methods that create `*Builder` instances (`Jwts.builder()`, `Jwts.parserBuilder()`, `Jwks.builder()`, etc) no longer use reflection.

Instead,`static` factories are created via reflection only once during initial `jjwt-api` classloading, and then `*Builder`s are created via standard instantiation using the `new` operator thereafter. This also benefits certain environments that may not have ideal `ClassLoader` implementations (e.g. Tomcat in some cases).

**NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names**.

See [Issue 988](https://github.com/jwtk/jjwt/issues/988).

### 0.12.6

This patch release:
Expand Down
25 changes: 21 additions & 4 deletions api/src/main/java/io/jsonwebtoken/Jwts.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.lang.Supplier;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.KeyPairBuilderSupplier;
Expand Down Expand Up @@ -1001,6 +1002,22 @@ private ZIP() {
}
}

// @since JJWT_RELEASE_VERSION
private static final Supplier<JwtBuilder> JWT_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder$Supplier");

// @since JJWT_RELEASE_VERSION
private static final Supplier<JwtParserBuilder> JWT_PARSER_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder$Supplier");

// @since JJWT_RELEASE_VERSION
private static final Supplier<HeaderBuilder> HEADER_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtHeaderBuilder$Supplier");

// @since JJWT_RELEASE_VERSION
private static final Supplier<ClaimsBuilder> CLAIMS_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.DefaultClaimsBuilder$Supplier");

/**
* A {@link Builder} that dynamically determines the type of {@link Header} to create based on builder state.
*
Expand All @@ -1018,7 +1035,7 @@ public interface HeaderBuilder extends JweHeaderMutator<HeaderBuilder>, X509Buil
* @since 0.12.0
*/
public static HeaderBuilder header() {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtHeaderBuilder");
return HEADER_BUILDER_SUPPLIER.get();
}

/**
Expand All @@ -1029,7 +1046,7 @@ public static HeaderBuilder header() {
* the JWT payload.
*/
public static ClaimsBuilder claims() {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaimsBuilder");
return CLAIMS_BUILDER_SUPPLIER.get();
}

/**
Expand Down Expand Up @@ -1057,7 +1074,7 @@ public static Claims claims(Map<String, Object> claims) {
* strings.
*/
public static JwtBuilder builder() {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder");
return JWT_BUILDER_SUPPLIER.get();
}

/**
Expand All @@ -1066,7 +1083,7 @@ public static JwtBuilder builder() {
* @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser}.
*/
public static JwtParserBuilder parser() {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder");
return JWT_PARSER_BUILDER_SUPPLIER.get();
}

/**
Expand Down
43 changes: 29 additions & 14 deletions api/src/main/java/io/jsonwebtoken/security/Jwks.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.jsonwebtoken.io.Parser;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.lang.Supplier;

/**
* Utility methods for creating
Expand All @@ -42,18 +43,30 @@ private Jwks() {
} //prevent instantiation

private static final String JWKS_BRIDGE_FQCN = "io.jsonwebtoken.impl.security.JwksBridge";
private static final String BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultDynamicJwkBuilder";
private static final String PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkParserBuilder";
private static final String SET_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetBuilder";
private static final String SET_PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetParserBuilder";

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
private static final Supplier<DynamicJwkBuilder<?, ?>> BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.security.DefaultDynamicJwkBuilder$Supplier");

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
private static final Supplier<JwkParserBuilder> PARSER_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkParserBuilder$Supplier");

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
private static final Supplier<JwkSetBuilder> SET_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkSetBuilder$Supplier");

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
private static final Supplier<JwkSetParserBuilder> SET_PARSER_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkSetParserBuilder$Supplier");

/**
* Return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair.
*
* @return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair.
*/
public static DynamicJwkBuilder<?, ?> builder() {
return Classes.newInstance(BUILDER_FQCN);
return BUILDER_SUPPLIER.get();
}

/**
Expand All @@ -69,7 +82,7 @@ private Jwks() {
* @return a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances.
*/
public static JwkParserBuilder parser() {
return Classes.newInstance(PARSER_BUILDER_FQCN);
return PARSER_BUILDER_SUPPLIER.get();
}

/**
Expand All @@ -87,7 +100,7 @@ public static JwkParserBuilder parser() {
* @return a new builder used to create {@link JwkSet}s
*/
public static JwkSetBuilder set() {
return Classes.newInstance(SET_BUILDER_FQCN);
return SET_BUILDER_SUPPLIER.get();
}

/**
Expand All @@ -103,7 +116,7 @@ public static JwkSetBuilder set() {
* @return a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances.
*/
public static JwkSetParserBuilder setParser() {
return Classes.newInstance(SET_PARSER_BUILDER_FQCN);
return SET_PARSER_BUILDER_SUPPLIER.get();
}

/**
Expand Down Expand Up @@ -370,19 +383,21 @@ public static final class OP {
private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyOperations";
private static final Registry<String, KeyOperation> REGISTRY = Classes.newInstance(IMPL_CLASSNAME);

private static final String BUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder";

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
private static final Supplier<KeyOperationBuilder> BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder$Supplier");

private static final String POLICY_BUILDER_CLASSNAME =
"io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder";
// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
private static final Supplier<KeyOperationPolicyBuilder> POLICY_BUILDER_SUPPLIER =
Classes.newInstance("io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder$Supplier");

/**
* Creates a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances.
*
* @return a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances.
*/
public static KeyOperationBuilder builder() {
return Classes.newInstance(BUILDER_CLASSNAME);
return BUILDER_SUPPLIER.get();
}

/**
Expand All @@ -391,7 +406,7 @@ public static KeyOperationBuilder builder() {
* @return a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances.
*/
public static KeyOperationPolicyBuilder policy() {
return Classes.newInstance(POLICY_BUILDER_CLASSNAME);
return POLICY_BUILDER_SUPPLIER.get();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
/**
* @since 0.12.0
*/
@SuppressWarnings("unused") // used via reflection via Jwts.claims()
public final class DefaultClaimsBuilder extends DelegatingClaimsMutator<ClaimsBuilder>
implements ClaimsBuilder {

Expand All @@ -34,4 +33,13 @@ public Claims build() {
// ensure a new instance is returned so that the builder may be re-used:
return new DefaultClaims(this.DELEGATE);
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwts class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<ClaimsBuilder> {
@Override
public ClaimsBuilder get() {
return new DefaultClaimsBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -818,4 +818,12 @@ private InputStream toInputStream(final String name, Payload payload) {
}
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwts class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<JwtBuilder> {
@Override
public JwtBuilder get() {
return new DefaultJwtBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,13 @@ public Header build() {
return new DefaultHeader(sanitizeCrit(m, false));
}
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwts class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<Jwts.HeaderBuilder> {
@Override
public Jwts.HeaderBuilder get() {
return new DefaultJwtHeaderBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,13 @@ public JwtParser build() {
encAlgs
);
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwts class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<JwtParserBuilder> {
@Override
public JwtParserBuilder get() {
return new DefaultJwtParserBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,13 @@ public J build() {
}
return super.build();
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwks class.
public static final class Supplier<K extends Key, J extends Jwk<K>> implements io.jsonwebtoken.lang.Supplier<DynamicJwkBuilder<K, J>> {
@Override
public DynamicJwkBuilder<K, J> get() {
return new DefaultDynamicJwkBuilder<>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,13 @@ public Parser<Jwk<?>> doBuild() {
JwkConverter<Jwk<?>> converter = new JwkConverter<>(supplier);
return new ConvertingParser<>(deserializer, converter);
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwks class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<JwkParserBuilder> {
@Override
public JwkParserBuilder get() {
return new DefaultJwkParserBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,13 @@ public JwkSetBuilder keys(Collection<Jwk<?>> c) {
public JwkSet build() {
return converter.applyFrom(this.map);
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwks class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<JwkSetBuilder> {
@Override
public JwkSetBuilder get() {
return new DefaultJwkSetBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,13 @@ public Parser<JwkSet> doBuild() {
JwkSetConverter converter = new JwkSetConverter(supplier, this.ignoreUnsupported);
return new ConvertingParser<>(deserializer, converter);
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwks class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<JwkSetParserBuilder> {
@Override
public JwkSetParserBuilder get() {
return new DefaultJwkSetParserBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,13 @@ public KeyOperationBuilder related(String related) {
public KeyOperation build() {
return new DefaultKeyOperation(this.id, this.description, this.related);
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwks class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<KeyOperationBuilder> {
@Override
public KeyOperationBuilder get() {
return new DefaultKeyOperationBuilder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,13 @@ public KeyOperationPolicyBuilder unrelated() {
public KeyOperationPolicy build() {
return new DefaultKeyOperationPolicy(Collections.immutable(getCollection()), this.unrelated);
}

// @since JJWT_RELEASE_VERSION per https://github.com/jwtk/jjwt/issues/988
@SuppressWarnings("unused") // used via reflection in the api module's Jwks class.
public static final class Supplier implements io.jsonwebtoken.lang.Supplier<KeyOperationPolicyBuilder> {
@Override
public KeyOperationPolicyBuilder get() {
return new DefaultKeyOperationPolicyBuilder();
}
}
}