eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

Keycloak is a third-party Identity and Access Management solution that can help us integrate authentication and authorization into our application.

In this tutorial, we’ll see a few examples of searching users in Keycloak.

2. Keycloak Configuration

First of all, we need to configure Keycloak. Let’s create an initial admin user named baeldung with the password secretPassword. Secondly, we need a realm to work on. Let’s use the master realm that already exists when we start Keycloak.

Finally, we need a client that we can use from our Spring Boot application. For this example, let’s use the admin-cli client that is created by default.

We need some users in the realm so that we can search for them later on. Let’s create a user with username “user1“, email “[email protected]“, and name “First UserI“. Now, we can repeat this pattern a few more times to have 10 users total.

3. Keycloak Admin Client

Keycloak has a REST API that can be used to access all of the features that are available on the Admin Console UI. We can use this with any client we’d like, but Keycloak provides a Java client that makes it easier. In our first examples, we’ll use this Java client from a Spring Boot application.

First of all, let’s add the keycloak-admin-client dependency to our pom.xml:

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-admin-client</artifactId>
    <version>21.0.1</version>
</dependency>

It’s important to ensure that the version of the client matches the version of the server. Mismatched versions might not work properly.

Now, let’s configure the client in our application. To do so, we need to create a Keycloak bean:

@Bean
Keycloak keycloak() {
    return KeycloakBuilder.builder()
      .serverUrl("http://localhost:8080")
      .realm("master")
      .clientId("admin-cli")
      .grantType(OAuth2Constants.PASSWORD)
      .username("baeldung")
      .password("secretPassword")
      .build();
}

In this builder, we configure the server URL, set the previously mentioned realm and client names, and provide the correct credentials.

Let’s create a service that’ll use this bean to access our Keycloak server:

@Service
public class AdminClientService {

    @Autowired
    Keycloak keycloak;

    @PostConstruct
    void searchUsers() {
        // ...
    }
}

After this, let’s start our application. Now we can start searching for users.

3.1. Search by Username

The Java client proves a very convenient way to search users by username. First, we need to reference the realm we’d like to use, then access the users and use the searchByUsername() method. Let’s create a method that searches for users and logs the result:

private static final String REALM_NAME = "master";

void searchByUsername(String username, boolean exact) {
    logger.info("Searching by username: {} (exact {})", username, exact);
    List<UserRepresentation> users = keycloak.realm(REALM_NAME)
      .users()
      .searchByUsername(username, exact);

    logger.info("Users found by username {}", users.stream()
      .map(user -> user.getUsername())
      .collect(Collectors.toList()));
}

Now, we can use it from our searchUsers() method with different parameters:

void searchUsers() {
    searchByUsername("user1", true);
    searchByUsername("user", false);
    searchByUsername("1", false);
}

The first search looks for an exact match of the “user1” username. The second one doesn’t require an exact match, so it returns all users with a username that contains the word “user“. The third one is similar and looks for usernames that contain the number “1”. Since we have 10 users with usernames from user1 to user10, the logs contain the following results:

12:20:22.295 [main] INFO  c.b.k.adminclient.AdminClientService - Searching users in Keycloak 21.0.1
12:20:22.296 [main] INFO  c.b.k.adminclient.AdminClientService - Searching by username: user1 (exact true)
12:20:22.341 [main] INFO  c.b.k.adminclient.AdminClientService - Users found by username [user1]
12:20:22.341 [main] INFO  c.b.k.adminclient.AdminClientService - Searching by username: user (exact false)
12:20:22.357 [main] INFO  c.b.k.adminclient.AdminClientService - Users found by username [user1, user10, user2, user3, user4, user5, user6, user7, user8, user9]
12:20:22.357 [main] INFO  c.b.k.adminclient.AdminClientService - Searching by username: 1 (exact false)
12:20:22.369 [main] INFO  c.b.k.adminclient.AdminClientService - Users found by username [user1, user10]

3.2. Search by Email

As we saw previously, it can be very easy to filter based on username. Fortunately, using email addresses as a filter is very similar to this. Let’s use the searchByEmail() method that accepts an email address and a boolean parameter for exact matches:

void searchByEmail(String email, boolean exact) {
    logger.info("Searching by email: {} (exact {})", email, exact);
    
    List<UserRepresentation> users = keycloak.realm(REALM_NAME)
      .users()
      .searchByEmail(email, exact);

    logger.info("Users found by email {}", users.stream()
      .map(user -> user.getEmail())
      .collect(Collectors.toList()));
}

Let’s test it with the “[email protected]” address and look for an exact match. There is only one result this time:

12:24:16.130 [main] INFO  c.b.k.adminclient.AdminClientService - Searching by email: [email protected] (exact true)
12:24:16.141 [main] INFO  c.b.k.adminclient.AdminClientService - Users found by email [[email protected]]

3.3. Search by Custom Attributes

In Keycloak, users can have custom attributes as well, not only the simple username and email. Let’s add a custom attribute called DOB for date of birth to user1 with the value “2000-01-05”.

Now, we can use the searchByAttributes() method from the admin client:

void searchByAttributes(String query) {
    logger.info("Searching by attributes: {}", query);
    
    List<UserRepresentation> users = keycloak.realm(REALM_NAME)
      .users()
      .searchByAttributes(query);

    logger.info("Users found by attributes {}", users.stream()
      .map(user -> user.getUsername() + " " + user.getAttributes())
      .collect(Collectors.toList()));
}

Let’s use the “DOB:2000-01-05” query to list the users with matching attributes:

13:19:51.091 [main] INFO  c.b.k.adminclient.AdminClientService - Searching by attributes: DOB:2000-01-05
13:19:51.103 [main] INFO  c.b.k.adminclient.AdminClientService - Users found by attributes [user1 {DOB=[2000-01-05]}]

3.4. Search by Group

Users can also belong to a group, and we can filter by this too. Let’s create a group named “Test Group” and add some members to it. Now, we can get the members of this group with the admin client:

void searchByGroup(String groupId) {
    logger.info("Searching by group: {}", groupId);

    List<UserRepresentation> users = keycloak.realm(REALM_NAME)
      .groups()
      .group(groupId)
      .members();

    logger.info("Users found by group {}", users.stream()
      .map(user -> user.getUsername())
      .collect(Collectors.toList()));
}

However, it’s important to notice that we used the group ID here and not the name. This way, we can list the members of a Keycloak group:

14:35:09.275 [main] INFO  c.b.k.adminclient.AdminClientService - Searching by group: c67643fb-514e-488a-a4b4-5c0bdf2e7477
14:35:09.290 [main] INFO  c.b.k.adminclient.AdminClientService - Users found by group [user1, user2, user3, user4, user5]

3.5. Search by Role

Searching by role is very similar to the previous method because we can list the users who have a specific role like we listed the members of a group. To do this, we need to assign a role to some of our users. Let’s create a role named “user” and assign it to “user1“. Now, we can implement the search functionality:

void searchByRole(String roleName) {
    logger.info("Searching by role: {}", roleName);

    List<UserRepresentation> users = keycloak.realm(REALM_NAME)
      .roles()
      .get(roleName)
      .getUserMembers();

    logger.info("Users found by role {}", users.stream()
      .map(user -> user.getUsername())
      .collect(Collectors.toList()));
}

Let’s see which users have this role:

12:03:23.788 [main] INFO  c.b.k.adminclient.AdminClientService - Searching by role: user
12:03:23.802 [main] INFO  c.b.k.adminclient.AdminClientService - Users found by role [user1]

4. Custom REST Endpoint

As we saw, Keycloak provides useful search functionalities by default, but in some cases, we might need something different or more complex. But there’s a solution to this, too. We can implement our own custom functionalities by adding a new API endpoint to Keycloak.

Let’s assume that we’d like to find users who are in a specific group and have a specific role as well. For example, we could find all the users who have the “project manager” role in the “software development department” group.

Firstly, we need a new class that extends from Keycloak’s RealmResourceProvider. Let’s implement our custom functionality here:

public class KeycloakUserApiProvider implements RealmResourceProvider {
    private final KeycloakSession session;

    public KeycloakUserApiProvider(KeycloakSession session) {
        this.session = session;
    }

    public void close() {
    }

    public Object getResource() {
        return this;
    }

    @GET
    @Produces({ MediaType.APPLICATION_JSON })
    public Stream<UserRepresentation> searchUsersByGroupAndRoleName(@QueryParam("groupName") @NotNull String groupName, @QueryParam("roleName") @NotBlank String roleName) {
        RealmModel realm = session.getContext().getRealm();

        Optional<GroupModel> groupByName = session.groups()
          .getGroupsStream(realm)
          .filter(group -> group.getName().equals(groupName))
          .findAny();

        GroupModel group = groupByName.orElseThrow(() -> new NotFoundException("Group not found with name " + groupName));

        return session.users()
          .getGroupMembersStream(realm, group)
          .filter(user -> user.getRealmRoleMappingsStream().anyMatch(role -> role.getName().equals(roleName)))
          .map(user -> ModelToRepresentation.toBriefRepresentation(user));
    }
}

After this, we need to tell Keycloak to use this class by configuring a RealmResourceProviderFactory:

public class KeycloakUserApiProviderFactory implements RealmResourceProviderFactory {
    public static final String ID = "users-by-group-and-role-name";

    @Override
    public RealmResourceProvider create(KeycloakSession session) {
        return new KeycloakUserApiProvider(session);
    }

    @Override
    public void init(Scope config) {
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {
    }

    @Override
    public void close() {
    }

    @Override
    public String getId() {
        return ID;
    }
}

Finally, we need to register this class by creating a file in the META-INF folder. There should be only one line in this file that contains the qualified name of our class. Let’s create the src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory file so it contains the name of our class:

com.baeldung.keycloak.customendpoint.KeycloakUserApiProviderFactory

Let’s build the project and copy the jar to the providers folder in Keycloak and run the build command to update the server’s provider registry:

kc build

We get the following output which means our custom provider was recognized and registered:

Updating the configuration and installing your custom providers, if any. Please wait.

Server configuration updated and persisted. Run the following command to review the configuration:
kc.bat show-config 

Let’s start the Keycloak instance again and access our custom API endpoint at http://localhost:8080/realms/master/users-by-group-and-role-name?groupName=Test%20Group&roleName=user.

This API endpoint successfully returns the user which satisfies both criteria, meaning that it belongs to the “Test Group” and has the role “user“:

[
    {
        "id": "2c59a20f-df38-4d14-8ff9-067ea30f7937",
        "createdTimestamp": 1678099525313,
        "username": "user1",
        "enabled": true,
        "emailVerified": true,
        "firstName": "First",
        "lastName": "User",
        "email": "[email protected]"
    }
]

5. Conclusion

In this article, we integrated our Spring Boot application with Keycloak using the Keycloak Admin Client to manage users. This offers an easy way to access the existing functionalities. Then, we created a custom REST endpoint to extend Keycloak with our custom feature that lets us implement any custom logic necessary.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LSS – NPI (cat=Security/Spring Security)
announcement - icon

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)