Repository navigation
45364 Migrate to adminClient for Java - #46973
Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates the Keycloak Client API v2 tests from raw HTTP client calls (HttpGet, HttpPut, HttpPost, HttpDelete) to using the Java admin client (Keycloak class). It also extracts AdminRootV2 from a concrete class into an interface with a DefaultAdminRootV2 implementation, and introduces a Clients wrapper class that bridges the legacy admin client with the new v2 API.
Changes:
- Extracted
AdminRootV2into an interface and createdDefaultAdminRootV2as the server-side implementation, enabling client-side proxy usage. - Added
Clientswrapper class andKeycloak.clients(realmName)method to provide access to both legacy client operations and the new v2 API through the admin client. - Migrated most test methods in
ClientApiV2Testfrom raw HTTP calls to useadminClient.clients(...).v2().
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/AdminRootV2.java |
Refactored from concrete class to interface |
rest/admin-v2/rest/src/main/java/org/keycloak/admin/api/DefaultAdminRootV2.java |
New implementation of AdminRootV2 interface |
integration/admin-client/src/main/java/org/keycloak/admin/client/wrapper/Clients.java |
New wrapper bridging legacy and v2 client APIs |
integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java |
Added clients(realmName) entry point |
integration/admin-client/pom.xml |
Added keycloak-admin-v2-rest dependency |
rest/admin-v2/tests/pom.xml |
Changed dependency to keycloak-admin-client-tests |
rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/AbstractClientApiV2Test.java |
Added realm-parameterized getClientsApiUrl overload |
rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java |
Migrated tests from raw HTTP to admin client |
You can also share your feedback on Copilot code review. Take the survey.
| setAuthHeader(getRequest); | ||
|
|
||
| // TODO this needs to be migrated to the adminClient, when we are able to construct Stream<BaseClientRepresentation>. Jackson config needs to be andjusted. |
There was a problem hiding this comment.
From my perspective the solution would be something like:
https://github.com/robson90/keycloak/blob/ef6cc6e18425ea1122dd235321f552cea8f7da3b/integration/admin-client/src/main/java/org/keycloak/admin/client/deserializer/StreamDeserializer.java#L14-L55
But as far as I understand from reading through docs of Jackson, Resteasy and Quarkus, you probably want to use something like this: https://quarkus.io/guides/rest#streaming-support
When debugging the failing test, I saw that the ResteasyProvider is invoking some synchronous invoke method for calling Keycloak. Probably that needs to be changed/configured on the Client-Level.
Please let me know what you think about the workaround with the StreamDeserializer.
CC: @lpradel
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
@robson90 thanks for the PR! I have a question, I might've missed it earlier, but what's exactly the decision behind putting the interfaces into the REST module instead of API module (in the same package)?
Although this seems to bloat only the test classpath, I think it make sense to pull as little transitive dependencies as possible to keep it clean. According to my quick check it is 197 vs. 66 transitive deps.
|
Dear @Pepo48 , regarding:
As far as I understood @vmuzikar wanted everything related to Admin Api V2 inside one module. Why the module structure has been chosen like this, idk. Regarding the dependencies, maybe I can add some exclusions. So we are only importing the relevant Interfaces and nothing more. I assume you used Thanks for the suggestions, will fix them ! |
I don't remember. :D But we could move it to the api module of course. |
|
@robson90 yes, I used |
|
@Pepo48 good catch, I used this to count the dependencies:
|
| import com.fasterxml.jackson.databind.JsonDeserializer; | ||
| import com.fasterxml.jackson.databind.deser.ContextualDeserializer; | ||
|
|
||
| public class StreamDeserializer extends JsonDeserializer<Stream<?>> implements ContextualDeserializer { |
There was a problem hiding this comment.
@shawkins Do you want to review too, especially the deserializer?
There was a problem hiding this comment.
I'm trying to understand this better. On the server side we are registering a StreamSerializer (
From the discussion in #46973 (comment) it would be good to understand exactly what quarkus is doing with the Stream (is it creating a full response buffer for error handling purposes?).
I know in some places we have resorted to using Response, and in particular the response stream directly for performance reasons
- these endpoints may also need something more idiomatic on the interface.There was a problem hiding this comment.
I assume Quarkus is handling Stream<> as like the return type would be List<ClientRepresentation>. Because when I debugged the failing test, I can see this Debug Log:
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 >> GET /admin/api/default/clients/v2 HTTP/1.1
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 >> Accept: application/json
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 >> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJDYnIxV1lTN2U4cGRJV00wZ1Nrck1qVTVCNi1ITndsb0ZuYk5mQ0otdS13In0.eyJleHAiOjE3NzM4Mzk1ODAsImlhdCI6MTc3MzgzOTUyMCwianRpIjoidHJsdGNjOmU5ZThjOWM2LWM2MWQtZjA5NS0xZmIyLTdlOGU0ZWJmNDk0YSIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvbWFzdGVyIiwic3ViIjoiZjc2YTQ5MjUtNTQxYi00YTk4LWFkODgtNTkxNDQ3YzQwZTg0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVtcC1hZG1pbiIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSJ9.O-3JuVtzbXOuudmwLNVOJkFpr4SE2P8dRz-AggfKM8zFE98XGyoCO3n1G3h0_WJjwFF9GSMBbuMPBEMNsumfhxKI-tgPUA08stuvCczoV2ZqPFAkbvUcWNX0u9s_RxYhVMRliKdugaSwAH2uD145dGC_YnGy7S8wmAURUo0tFShy7UIslO5_kytXw0G6Cf9fCg327yr0-zzI7S-jdV2qmHHYdNlJXCxRWASs6slVrpAbLlZf55YjmVKkGDyXebmeC6T08kCZ19WnqZjo3knHi6SLVFnAow0hGLVLniFxbGTCn7t7g-HTw_BVsCQdvbjtI4joesj17d6YFgoZrYF1YQ
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 >> Host: localhost:8080
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 >> Connection: Keep-Alive
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 >> User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.18)
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 >> "GET /admin/api/default/clients/v2 HTTP/1.1[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 >> "Accept: application/json[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 >> "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJDYnIxV1lTN2U4cGRJV00wZ1Nrck1qVTVCNi1ITndsb0ZuYk5mQ0otdS13In0.eyJleHAiOjE3NzM4Mzk1ODAsImlhdCI6MTc3MzgzOTUyMCwianRpIjoidHJsdGNjOmU5ZThjOWM2LWM2MWQtZjA5NS0xZmIyLTdlOGU0ZWJmNDk0YSIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvbWFzdGVyIiwic3ViIjoiZjc2YTQ5MjUtNTQxYi00YTk4LWFkODgtNTkxNDQ3YzQwZTg0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVtcC1hZG1pbiIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSJ9.O-3JuVtzbXOuudmwLNVOJkFpr4SE2P8dRz-AggfKM8zFE98XGyoCO3n1G3h0_WJjwFF9GSMBbuMPBEMNsumfhxKI-tgPUA08stuvCczoV2ZqPFAkbvUcWNX0u9s_RxYhVMRliKdugaSwAH2uD145dGC_YnGy7S8wmAURUo0tFShy7UIslO5_kytXw0G6Cf9fCg327yr0-zzI7S-jdV2qmHHYdNlJXCxRWASs6slVrpAbLlZf55YjmVKkGDyXebmeC6T08kCZ19WnqZjo3knHi6SLVFnAow0hGLVLniFxbGTCn7t7g-HTw_BVsCQdvbjtI4joesj17d6YFgoZrYF1YQ[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 >> "Host: localhost:8080[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 >> "Connection: Keep-Alive[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 >> "User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.18)[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 >> "[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "HTTP/1.1 200 OK[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "content-length: 4179[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "Content-Type: application/json;charset=UTF-8[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "Referrer-Policy: no-referrer[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "Strict-Transport-Security: max-age=31536000; includeSubDomains[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "X-Content-Type-Options: nosniff[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "X-Frame-Options: SAMEORIGIN[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "X-Robots-Tag: none[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "[\r][\n]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.wire] (main) http-outgoing-1 << "[{"clientId":"account","displayName":"${client_account}","enabled":true,"appUrl":"/realms/default/account/","redirectUris":["/realms/default/account/*"],"roles":["manage-account","view-applications","view-consent","view-groups","manage-account-links","manage-consent","delete-account","view-profile"],"loginFlows":["STANDARD"],"protocol":"openid-connect"},{"clientId":"account-console","displayName":"${client_account-console}","enabled":true,"appUrl":"/realms/default/account/","redirectUris":["/realms/default/account/*"],"loginFlows":["STANDARD"],"protocol":"openid-connect"},{"clientId":"admin-cli","displayName":"${client_admin-cli}","enabled":true,"loginFlows":["DIRECT_GRANT"],"protocol":"openid-connect"},{"clientId":"broker","displayName":"${client_broker}","enabled":true,"roles":["read-token"],"loginFlows":["STANDARD"],"auth":{"method":"client-secret"},"protocol":"openid-connect"},{"clientId":"mixed-test-oidc","description":"OIDC client for mixed protocol test","enabled":true,"loginFlows":["STANDARD","DIRECT_GRANT"],"webOrigins":["http://localhost:3000","http://localhost:4000"],"protocol":"openid-connect"},{"clientId":"mixed-test-saml","description":"SAML client for mixed protocol test","enabled":true,"nameIdFormat":"email","forceNameIdFormat":false,"includeAuthnStatement":true,"signDocuments":true,"signAssertions":true,"clientSignatureRequired":true,"forcePostBinding":true,"frontChannelLogout":false,"signatureAlgorithm":"RSA_SHA256","signatureCanonicalizationMethod":"http://www.w3.org/2001/10/xml-exc-c14n#","signingCertificate":"MIIErTCCApUCBgGdARMXLDANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9taXhlZC10ZXN0LXNhbWwwHhcNMjYwMzE4MTMxMDIxWhcNMjkwMzE4MTMxMjAwWjAaMRgwFgYDVQQDDA9taXhlZC10ZXN0LXNhbWwwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiv3/qyQcSTirZ7k4f9gmRwHvcmGU3Cba7EI/no3EVagcBDaeFpZPRax5eUTDgPRQQ7c46L0FEljQ+VAZ8eZpdIhzBTS0jny9c+1ZKCum4H1gGh3ePrLo5iMiNZ9xJRq+iAsU7BhycuR5uteOqH6bRxSlO9rkgcyfUA+2Cl/rQLiOaqSU/HxBVXI62pe+E/2mixQlS1iKH8Jt2KmHK3ELprH2678mU5LlHpznx5iMJX5Ih/rsGLGXTDA3K2MPgPsHn+ayyTFkujcltDG/p71yyCBr5ymhAx/xQ4bSQbFDLIcba8yR7vJpVqxVHk/BjEUrNtMOMm1MQA3AxupqAaP/aRyATSgUlROzEukhz/ghWSRGMo3cEjq3oYXMQgrWbnCVIeWacVzbJzxYpQR5ABlhY9If3VymXgQKGAjA3chDd8rHHwr+oPTzAEk8ZjZkWfOogX5gYFUchK9XDdelv338c94AcSz5/eUImkzNNIyCtNa+PhGqHfBcXOlOLB3J7mH6Hzn/vNXxoyrxsT1hR/Fm4iL8ZVQ8btdXv3jZ14m8uEUoMZc0zJfq+jGDxzV1QF/VJMFFgYSCduawGyycUKG+pGfRQeZxpqFmtV0t+jBgJecTdj/TOXbhXG9+7ptngDIzxEZgBBRqSoptOKYL6mwy1AXa1NCO4TDlinEM+jvhwkQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCGpbWga8PutsKZu5vXWp8iDFQDHnYxvdJuBiiqA8dPuCkqC8sxwqUkQn6SruyLRnry17N+BLYzlECxK3aBeLmCa7fiWdhS92NSDdyowqA5X69XMTStmzK9MuIdqmfhHO+AsrOZVNIizUI/sPRAb+SwITJBLNnIyyKH+LNDNWsB2QG+vLYVje/qDnuXBWkn2bsckf7x9kXq+LydPKzI0Aj4mi+4ge4R4PPTI9VTgxaPd+IASh/XtbvFhWLrEMcS74pppST+QAPMKc6sWAMf6m3OhCbq7DQPPaEg3yqUMNoWlnD4CXkZc4503QHUjy1/wkg1x6p6dzs3RCZRt4nN8tM19PH5K0iiArlKWvjVOB9JAnIfkKhkbCieNmeImSt4bRhKlqkH8KsAfa8HRfEkzh+gHINZEk8Uk0EmNctsz2IbFultKIBLkXoFspb3ILy8WHmggwtlRl19bzQYuimIamOLX/nrnEgiCWd7A9IzqgX/C5fYQaWWVSHPE4EE1wCqxCWz0wPpj98KjTwiaP+GySj3cfb2xlnOe4RUK9vLILWPe0ofWmlVB1oJ/hQjtpOhy6+EO4t6vAKIGKCUz05XNnjV7sPfbiKeTaeOO7USMNRLUTjgR8+UpdEBF5vxV9pQPlKNyYlaE7nPFR8U65s2W4wQH20cdjE5NRdE+MtukZQgdQ==","allowEcpFlow":false,"protocol":"saml"},{"clientId":"myclient","enabled":true,"loginFlows":["SERVICE_ACCOUNT","STANDARD"],"auth":{"method":"client-secret","secret":"mysecret"},"serviceAccountRoles":["default-roles-default"],"protocol":"openid-connect"},{"clientId":"realm-management","displayName":"${client_realm-management}","enabled":true,"roles":["view-realm","view-identity-providers","manage-identity-providers","impersonation","realm-admin","create-client","manage-users","query-realms","view-authorization","query-clients","query-users","manage-events","manage-realm","view-events","view-users","view-clients","manage-authorization","manage-clients","query-groups"],"loginFlows":["STANDARD"],"auth":{"method":"client-secret"},"protocol":"openid-connect"},{"clientId":"security-admin-console","displayName":"${client_security-admin-console}","enabled":true,"appUrl":"/admin/default/console/","redirectUris":["/admin/default/console/*"],"loginFlows":["STANDARD"],"webOrigins":["+"],"protocol":"openid-connect"}]"
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << HTTP/1.1 200 OK
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << content-length: 4179
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << Content-Type: application/json;charset=UTF-8
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << Referrer-Policy: no-referrer
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << Strict-Transport-Security: max-age=31536000; includeSubDomains
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << X-Content-Type-Options: nosniff
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << X-Frame-Options: SAMEORIGIN
2026-03-18 14:12:01,626 DEBUG [org.apache.http.headers] (main) http-outgoing-1 << X-Robots-Tag: none
as the payload is looking like a list.
I think there are two different things that are important:
- What is the return type from Keycloak.
- How does the adminClient interact with Keycloak. As in the discussion above, the adminClient is calling the endpoint with a synchronize invoke method. Which means every performance benefit is gone, as it is waiting for the complete response.
There was a problem hiding this comment.
I assume Quarkus is handling Stream<> as like the return type would be List
Yes, can confirm the StreamSerializer just writes an array.
From a server side perspective I confirmed that is happening in a streaming manner as the serialization is directly into the output stream.
What is the return type from Keycloak.
When we are returning a "list" the interface will be one of List (array), Stream, or Multi.
Unless I'm missing something, the response body would be identical no matter the type. The client proxy logic only knows about List by default, but we don't want to use that when the server logic is actually streaming.
Based upon what you have seen here, the use of Stream or Multi will require a custom deserializer.
In more exotic scenarios we'll return a Response. I haven't checked how a RestEasy proxy handles a raw Response, but that would at least require additional client side logic to consume.
Switching to an InputStream would not good either as that negates the server side simplicity of directly returning the objects.
How does the adminClient interact with Keycloak. As in the discussion above, the adminClient is calling the endpoint with a synchronize invoke method. Which means every performance benefit is gone, as it is waiting for the complete response.
We'd need async parsing support as well.
The only simple solutions here seem to be:
- keep adding deserializers as needed, and double check how Response is handled via a client side proxy.
- introduce or generate client side specific interfaces such that Stream is replaced by List
Another thought is that successful response codes are basically not available via the proxy (or can they be obtained somehow?). Our PUT can return differentiated codes (created vs updated), so you won't know which occurred.
There was a problem hiding this comment.
2. introduce or generate client side specific interfaces such that Stream is replaced by List
That's something we did with v1. If possible, I'd like to avoid doing that again (especially if not generated but maintained manually).
I'm quite surprised resteasy doesn't support a basic stream deserializer OOTB. So we'll need to add custom one but it's not ideal of course.
In more exotic scenarios we'll return a Response. I haven't checked how a RestEasy proxy handles a raw Response, but that would at least require additional client side logic to consume.
There was a problem hiding this comment.
Ah, so that the concern about alternative success codes as well - any time we have to return one we'll be using a response. So then we'll just need some helper logic for consuming Responses.
Something we need to do in a follow-up is add APIResponses to everything that is returning a Response:
@Operation(summary = "Create or update a client", description = "Creates or updates a client in the realm")
@APIResponses(value = {
@APIResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = BaseClientRepresentation.class))),
@APIResponse(description = "created", responseCode = "201", content = @Content(schema = @Schema(implementation = BaseClientRepresentation.class))) })
The client side handling of a raw Response is still not ideal.
There is a resteasy reactive RestResponse construct that is typed - are we allowed to use that or do we need to stick to plain jax-rs? I also haven't check yet how that is handled on the client side.
Alternatively if we can't use that, we could consider introducing a TypedResponse class that extends Response (which may not be allowable) and delegates to an actual response, then wrap the target proxy with another proxy that detects when a method is called that returns a TypedResponse and does the conversion there such that getEntity returns the expected type.
There was a problem hiding this comment.
Something we need to do in a follow-up is add APIResponses to everything that is returning a Response
Captured as #47365.
The client side handling of a raw Response is still not ideal.
+1
There is a resteasy reactive RestResponse construct that is typed - are we allowed to use that or do we need to stick to plain jax-rs?
AFAIK we need to be as platform/lib agnostic as possible in our endpoints.
Alternatively if we can't use that, we could consider introducing a TypedResponse class
That would sound good. But we could do that as a follow-up.
There was a problem hiding this comment.
we could consider introducing a TypedResponse class
Captured as #47367.
Co-authored-by 10BC8 : Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com> Update rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java Co-authored-by: Peter Zaoral <pepo48@gmail.com> Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com>
Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com>
Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com>
Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com>
| // The client must work with the newer versions of Keycloak server, which might contain the JSON fields not yet known by the client. So unknown fields will be ignored. | ||
| objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | ||
|
|
||
| objectMapper.registerModule(STREAM_MODULE); |
There was a problem hiding this comment.
As discussed on the meeting, the concerns about including it into the admin client - we could conditionally register it if the Client V2 feature is enabled. It'll help to decrease the probability that something is broken for the admin client core itself.
cc: @robson90 @keycloak/cloud-native WDYT?
There was a problem hiding this comment.
IMHO the safest path here would be to make it return List<> for now.
There was a problem hiding this comment.
Based upon what the module is doing, it is safe to leave this as is.
Swiching to List is not desirable as that can have a heavier memory footprint server side.
There was a problem hiding this comment.
It's an admin client, it doesn't know what features are enabled. We could, hoverer, hide it behind some system property toggle?
There was a problem hiding this comment.
If @shawkins has reviewed the logic and has the context, I'm ok to leave it as is.
There was a problem hiding this comment.
Swiching to List is not desirable as that can have a heavier memory footprint server side.
I understand that, but you are moving the memory footprint from the Server to the Client, as the Client is creating a list from it and then streams it.
Somewhere above are the Debug logs from the Payload, that Keycloak is returning. For me that looks like a normal List<> as response. Im unsure, if we are getting any real benefit here.
Im fine with Stream<> for now.
Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com>
Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com>
| public String getClientsApiUrl() { | ||
| return "http://localhost:8080/admin/api/%s/clients/v2".formatted(getRealmName()); | ||
| } | ||
|
|
There was a problem hiding this comment.
Just a nitpick. Not necessary change.
|
@mabartos Do you want to give it another look? |
Closes #45364
As requested here are the changes.
Please take a look and give feedback: @vmuzikar & @mabartos