Skip to content

Add Jackson 3 support for Keycloak Admin Client#50848

Open
michalvavrik wants to merge 1 commit into
keycloak:mainfrom
michalvavrik:feature/kc-admin-client-jackson-3
Open

Add Jackson 3 support for Keycloak Admin Client#50848
michalvavrik wants to merge 1 commit into
keycloak:mainfrom
michalvavrik:feature/kc-admin-client-jackson-3

Conversation

@michalvavrik

@michalvavrik michalvavrik commented Jul 13, 2026

Copy link
Copy Markdown
Member
  • Closes: KeyCloak Admin Client - Support Jackson 3 #48216
  • introduces a new Keycloak Admin client that relies on Jackson 3
  • adds a PR CI tests for Jackson 3-based module for both Admin API v1 and v2
  • Jackson-2 still exist, but a core Jackson-version-agnostic client module was extracted to a new module
  • Keycloak core representations are now using Jackson-version-agnostic abstractions only and there is a test that enforces no Jackson 2 specific classes are used in the representation

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Jackson 3 support to the Keycloak Admin Client while retaining Jackson 2 compatibility.

Changes:

  • Extracts Jackson-independent client code into keycloak-admin-client-core.
  • Adds Jackson 3 REST, mapper, serializer, and service-provider implementations.
  • Introduces JSON abstractions and Jackson 3 CI profiles for Admin API v1 and v2.

Reviewed changes

Copilot reviewed 64 out of 148 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
.github/actions/conditional/action.yml Exposes the Jackson 3 CI condition.
.github/actions/conditional/conditions Selects changes requiring Jackson 3 tests.
.github/workflows/ci.yml Runs Jackson 3 admin-client tests.
integration/pom.xml Registers the new client modules.
integration/admin-client/pom.xml Makes Jackson 2 client depend on shared core.
integration/admin-client-core/pom.xml Defines the Jackson-independent client module.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/Config.java Moves client configuration into shared core.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/CreatedResponseUtil.java Moves response utilities into shared core.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/Keycloak.java Moves main client implementation into shared core.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java Moves the client builder into shared core.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/spi/ResteasyClientProvider.java Defines the REST client provider SPI.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/token/TokenManager.java Moves token management into shared core.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/token/TokenService.java Moves the token endpoint proxy into shared core.
integration/admin-client-core/src/main/java/org/keycloak/admin/client/resource/* Moves Admin REST resource interfaces and filters into shared core.
integration/admin-client-jackson3/pom.xml Defines the Jackson 3 client module and dependencies.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/JacksonProvider3.java Configures the Jackson 3 REST provider.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/Jackson3JsonMapper.java Implements the mapper SPI for Jackson 3.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/Jackson3RawJsonValueSupport.java Implements raw JSON operations for Jackson 3.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/KeycloakAnnotationIntrospector3.java Maps version-neutral annotations to Jackson 3 handlers.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/MultivaluedHashMapValueDeserializer3.java Adds Jackson 3 multivalued-map deserialization.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/MultivaluedHashMapValueSerializer3.java Adds Jackson 3 multivalued-map serialization.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/ResteasyClientClassicProvider3.java Supplies the Jackson 3 RESTEasy client.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/StreamDeserializer3.java Adds stream response deserialization.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/StringListMapDeserializer3.java Adds flexible string-list map deserialization.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/StringOrArrayDeserializer3.java Reads scalar-or-array string values.
integration/admin-client-jackson3/src/main/java/org/keycloak/admin/client/jackson3/StringOrArraySerializer3.java Writes scalar-or-array string values.
integration/admin-client-jackson3/src/main/resources/META-INF/services/org.keycloak.admin.client.spi.ResteasyClientProvider Registers the Jackson 3 REST provider.
integration/admin-client-jackson3/src/main/resources/META-INF/services/org.keycloak.json.KeycloakJsonMapper Registers the Jackson 3 mapper.
integration/admin-client-jackson3/src/main/resources/META-INF/services/org.keycloak.json.RawJsonValueSupport Registers Jackson 3 raw JSON support.
core/src/main/java/org/keycloak/json/KeycloakJsonMapper.java Defines a version-neutral mapper SPI.
core/src/main/java/org/keycloak/json/KeycloakJsonMapperFactory.java Loads the active mapper implementation.
core/src/main/java/org/keycloak/json/RawJsonValue.java Introduces a version-neutral raw JSON wrapper.
core/src/main/java/org/keycloak/json/RawJsonValueSupport.java Defines raw JSON navigation operations.
core/src/main/java/org/keycloak/json/Jackson2JsonMapper.java Implements the mapper SPI for Jackson 2.
core/src/main/java/org/keycloak/json/Jackson2RawJsonValueSupport.java Implements raw JSON operations for Jackson 2.
core/src/main/java/org/keycloak/json/StringOrArray.java Introduces a scalar-or-array annotation.
core/src/main/java/org/keycloak/json/StringListMap.java Introduces a flexible string-list map annotation.
core/src/main/java/org/keycloak/json/MultivaluedHashMapValue.java Introduces a multivalued-map annotation.
core/src/main/resources/META-INF/services/org.keycloak.json.KeycloakJsonMapper Registers the Jackson 2 mapper.
core/src/main/resources/META-INF/services/org.keycloak.json.RawJsonValueSupport Registers Jackson 2 raw JSON support.
core/src/main/java/org/keycloak/representations/** Replaces Jackson 2-specific representation types and annotations.
core/src/main/java/org/keycloak/jose/jwe/JWE.java Uses the mapper SPI for JWE headers.
core/src/main/java/org/keycloak/jose/jws/JWSBuilder.java Uses the mapper SPI for JWS serialization.
core/src/main/java/org/keycloak/jose/jws/JWSHeader.java Uses the mapper SPI for header serialization.
core/src/main/java/org/keycloak/jose/jws/JWSInput.java Uses the mapper SPI when reading headers.
core/src/main/java/org/keycloak/util/JWKSUtils.java Uses the mapper SPI for thumbprints.
core/src/main/java/org/keycloak/util/TokenUtil.java Uses the mapper SPI for token processing.
core/src/test/java/org/keycloak/json/JacksonAgnosticRepresentationsTest.java Checks representations for Jackson 2-specific imports.
services/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java Unwraps version-neutral client-policy configuration.
test-framework/builders/src/main/java/org/keycloak/testframework/realm/ClientPolicyBuilder.java Wraps condition configuration in RawJsonValue.
test-framework/builders/src/main/java/org/keycloak/testframework/realm/ClientProfileBuilder.java Wraps executor configuration in RawJsonValue.
tests/base/pom.xml Adds Jackson 3 Admin API v1 smoke tests.
tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCIssuerTestBase.java Adapts OID4VC client-policy configuration.
rest/admin-v2/tests/pom.xml Adds the Jackson 3 Admin API v2 profile.
rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientPoliciesV2Test.java Adapts v2 policy configuration to raw JSON.
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/AbstractClientPoliciesTest.java Unwraps configuration for assertions.
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientPoliciesUtil.java Wraps test policy configuration.

private static final Set<String> ALLOWLISTED_FILES = new HashSet<>(Arrays.asList(
"MultivaluedHashMapValueSerializer.java",
"MultivaluedHashMapValueDeserializer.java",
"AuthorizationSchema.java"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is Jackson 3 support for client, server specific things are not relevant. That would make PR much larger.

Comment thread core/src/main/java/org/keycloak/json/RawJsonValue.java Outdated
Copilot AI review requested due to automatic review settings July 14, 2026 00:38
@michalvavrik
michalvavrik force-pushed the feature/kc-admin-client-jackson-3 branch from 6f14125 to 0c0dfde Compare July 14, 2026 00:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 151 changed files in this pull request and generated 7 comments.

Comment thread core/src/main/java/org/keycloak/json/KeycloakJsonMapperFactory.java Outdated
Comment thread core/src/main/java/org/keycloak/json/RawJsonValueSupport.java Outdated
}

public JsonNode getConfiguration() {
public RawJsonValue getConfiguration() {

@michalvavrik michalvavrik Jul 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't keep the same type if it is not present in Jackson 3. It is "breaking", but hardly an issue considering I provide same methods that are relevant. If you tightly couple representation with underlying libraries, this is what you get.

}

public JsonNode getConfiguration() {
public RawJsonValue getConfiguration() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment, same answer, read here: #50848 (comment)

Comment on lines +63 to +65
<profile>
<id>jackson3</id>
<dependencies>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keycloak-admin-client-tests is NOT on the classpath, therefore ResteasyClientClassicProvider and JacksonProvider that live in that module cannot be loaded. keycloak-admin-v2-services needs Jackson 2 for server, but it is not used by the client as Jackson 3 ignores Jackson 2 stuff which is relevant in this context.

Comment thread tests/base/pom.xml
Comment on lines +206 to +208
<profile>
<id>jackson3</id>
<dependencies>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, answered here: #50848 (comment)

Comment thread integration/admin-client-jackson3/pom.xml Outdated
Copilot AI review requested due to automatic review settings July 14, 2026 00:48
@michalvavrik
michalvavrik force-pushed the feature/kc-admin-client-jackson-3 branch from 0c0dfde to 34ac05a Compare July 14, 2026 00:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 151 changed files in this pull request and generated 2 comments.

Comment thread .github/actions/conditional/conditions
@michalvavrik

Copy link
Copy Markdown
Member Author

I am actually not completely sure which labels should this PR have, so I left such one I am sure and someone else can add additional.

Copilot AI review requested due to automatic review settings July 14, 2026 09:40
@michalvavrik
michalvavrik force-pushed the feature/kc-admin-client-jackson-3 branch from 34ac05a to 770d8f7 Compare July 14, 2026 09:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 151 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings July 14, 2026 10:11
@michalvavrik
michalvavrik force-pushed the feature/kc-admin-client-jackson-3 branch from 770d8f7 to 08e5a91 Compare July 14, 2026 10:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum diff size. Try reducing the number of changed files and lines, and requesting a review from Copilot again.

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.cluster.RealmInvalidationClusterTest#crudWithFailover

Keycloak CI - Store IT (mssql)

org.opentest4j.AssertionFailedError: expected: <false> but was: <true>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertFalse.failNotFalse(AssertFalse.java:63)
	at org.junit.jupiter.api.AssertFalse.assertFalse(AssertFalse.java:36)
...

Report flaky test

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@vmuzikar

Copy link
Copy Markdown
Contributor

We'll need to reviews from Core teams, especially @mposolda.

@vmuzikar
vmuzikar requested a review from mposolda July 14, 2026 14:31
@shawkins

Copy link
Copy Markdown
Contributor

I made a first pass through the changes and it seems like a good approach - great job @michalvavrik. I'll take a deeper look tomorrow.

shawkins
shawkins previously approved these changes Jul 15, 2026

@shawkins shawkins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit on convertValue, otherwise I think it looks good.

The comments about this being a breaking api change are of course correct. The only way around that would be:

  • see if we can create / maintain a Jackson 2 to Jackson 3 shim - which seems pretty painful.
  • have the admin client jar creation process also create a fork of the core that replaces usage of RawJsonValue with JsonNode. However this further complicates the admin client dependencies, and would require a good bit of discipline to ensure the references could be directly replaced (for example remove how the type is currently being used in hashcode and equals methods).

Seems like that would be a call for @mposolda to make on whether full compatibility is required.

Comment thread core/src/main/java/org/keycloak/json/RawJsonValue.java
Copilot AI review requested due to automatic review settings July 15, 2026 20:24
@michalvavrik
michalvavrik force-pushed the feature/kc-admin-client-jackson-3 branch from 08e5a91 to b46fc8d Compare July 15, 2026 20:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 151 changed files in this pull request and generated 2 comments.

Comment on lines +67 to +69
core/src/main/java/org/keycloak/json/ ci-jackson3
core/src/main/java/org/keycloak/representations/ ci-jackson3
core/src/main/resources/META-INF/services/ ci-jackson3

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Jackson 3 gate still omits changed core paths that execute in this client: DPoPAuthFilter calls DPoPGenerator, which uses the modified jose/jws/JWSBuilder, and this PR also routes TokenUtil/JWKSUtils through the new mapper. A later change to those paths can therefore bypass the only Jackson 3 job; add the JOSE path and the relevant utility files to this condition.

I think it would be misleading, because I don't see which client-side tests are verifying it. It is not straighforward, I could be missing something of course.

Comment thread tests/base/pom.xml
Comment on lines +255 to +261
<includes>
<include>**/AdminClientTest.java</include>
<include>**/RealmCreateTest.java</include>
<include>**/UserCreateTest.java</include>
<include>**/ClientTest.java</include>
<include>**/GroupTest.java</include>
</includes>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find a DPoP test in the new test framework which actually tests this DPoPAuthFilter. This is just about writeValueAsBytes which is trivial, I don't think there is an issue.

@michalvavrik

Copy link
Copy Markdown
Member Author

I think @mposolda is OOO, I'll ping him next week and ask if he can find a time for a review.

edewit
edewit previously approved these changes Jul 21, 2026
@Pepo48

Pepo48 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

@michalvavrik I looked at the ServiceLoader behaviour when both Jackson 2 and 3 are on the classpath (-Pjackson3 tests) - both providers load fine. ServiceConfigurationError only fires when a dependency is completely missing. The factory just returns whichever ServiceLoader iterates first, which depends on classpath order.

It today because Maven consistently orders keycloak-core (Jackson 2) first. My concern is if that order ever flips, wrong provider gets picked silently.

Would it be worth adding a priority mechanism to KeycloakJsonMapper/RawJsonValueSupport so selection is by design, not by accident?

Edit: I noticed that Copilot already raised this above, but if I understand correctly, only the missing-dependency case has been covered, not the both-present case (which I think is a valid concern for the CI environment).

Other than this, it looks good from my perspective. Thanks!

@michalvavrik

michalvavrik commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

@Pepo48 I thought about it yesterday and today again with stellar output: "Kinda".

Let my clarify my starting points in case we do not share them:

  1. What you / Copilot describe is test concern only, not a users concern. Due to how our new test framework works. When users are using keycloak-admin-client-jackson3, neither resteasy-jackson2-provider nor core/src/main/resources/META-INF/services/org.keycloak.json.KeycloakJsonMapper / core/src/main/resources/META-INF/services/org.keycloak.json.RawJsonValueSupport
  2. For our test scenarios then:
  • ResteasyClientClassicProvider3 is always used, therefore the question is which of service-loaded objects are using, whether Jackson2JsonMapper and Jackson2RawJsonValueSupport or Jackson3JsonMapper and Jackson3RawJsonValueSupport
  • There is no risk server is using Jackson 3. These tests are run with DistributionKeycloakServer (unless someone needs to debug Jackson 3 test profile with embedded, in which case they are on their own), therefore server runs in a separate JVM process and the distribution zip doesn't contain Jackson 3, nor it is copied in.
  • There is no situation where both Jackson 3 and Jackson 2 providers are present and we want to use Jackson 2

If we can agree on that ^^, I will add priority to Jackson 3 RawJsonValue and KeycloakJsonMapper to win and then ask all three of you (@shawkins , @Pepo48 , @edewit ) to re-check (only change will be that priority and rebase).

Working on it now.

Closes: keycloak#48216

Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
Copilot AI review requested due to automatic review settings July 24, 2026 15:19
@michalvavrik
michalvavrik force-pushed the feature/kc-admin-client-jackson-3 branch from 6c85c90 to 0324a48 Compare July 24, 2026 15:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 151 changed files in this pull request and generated 2 comments.

try {
executor.setConfiguration(JsonSerialization.mapper.readValue(JsonSerialization.mapper.writeValueAsBytes(config), JsonNode.class));
} catch(IOException e) {
executor.setConfiguration(RawJsonValue.of(JsonSerialization.mapper.readValue(JsonSerialization.mapper.writeValueAsBytes(config), JsonNode.class)));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If tests are passing, what is the point? Jackson 3 profile is a temporary smoke check.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to make such changes as it will smell to someone without context, but I'll address this probably, it sounds like it makes sense. Hope its OK.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, no, it is change for default tests as well and I don't want to affect them, I want to keep original test assertion. I'll keep it as is. I run all tests and they are passing locally.

}
try {
condition.setConfiguration(JsonSerialization.mapper.readValue(JsonSerialization.mapper.writeValueAsBytes(config), JsonNode.class));
condition.setConfiguration(RawJsonValue.of(JsonSerialization.mapper.readValue(JsonSerialization.mapper.writeValueAsBytes(config), JsonNode.class)));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredAndRegisterClick

Keycloak CI - Forms IT (chrome)

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

KeyCloak Admin Client - Support Jackson 3

6 participants