Keycloak with Spring Boot Applications: Step-by-Step Guide
In this tutorial, you will learn how to secure a Spring Boot application using Keycloak and JWT tokens. We will walk through how to quickly set up a Keycloak server and configure a Spring Boot application to authenticate and authorize users via Keycloak. This comprehensive guide is ideal for developers looking to integrate robust, enterprise-grade authentication into their Spring Boot applications.
The examples use Spring Boot 4.1.x, Spring Security 7, and Keycloak 26.7.x. The same OAuth 2.0 Resource Server approach also applies to supported Spring Boot 3.5 applications. Spring Boot 4.1.1 is currently the latest stable Spring Boot release, while Keycloak 26.7.3 is the latest Keycloak maintenance release at the time of writing.
Prerequisites
Before starting, ensure you have the following installed:
- Java 17 or later. Spring Boot 4 supports Java 17 and has first-class support for Java 25.
- Maven 3.8+.
- Docker or Podman, if you prefer running Keycloak in a container.
- Keycloak 26.7.x or a later supported Keycloak release.
- Basic understanding of Spring Boot, Spring Security, OAuth 2.0, and OpenID Connect.
Step 1: Setting Up Keycloak
To run this example, we will configure a local Keycloak server with a custom Realm called ApplicationRealm.
1.1 Download and Install Keycloak
You can download Keycloak from the official Keycloak website or use Docker to run it quickly:
docker run --name keycloak -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin
quay.io/keycloak/keycloak:26.7.3 start-dev
The start-dev command is intended for local development. It is not a production configuration. For production deployments, configure TLS, a persistent database, suitable hostname settings, secrets management, and the appropriate Keycloak production startup options.
In this example, we will use the following Keycloak configuration:
- Development admin credentials:
admin/admin - Application Realm:
ApplicationRealm - Client ID:
my-client - Client type: OpenID Connect confidential client for the local example
- User:
user1with theadminrole
Do not use these example credentials in a production environment. Production client secrets and administrator credentials should be generated securely and stored outside source control.
To learn more about using Keycloak with Docker check this tutorial: Keycloak Using Docker and Docker Compose (2025 Edition)
1.2 Configure Keycloak via CLI
You can automate the configuration using the Keycloak Admin CLI (kcadm.sh):
The client can be created from the Keycloak Admin Console. This is preferable to embedding a client secret in a shell script that may later be committed to source control.
Create an OpenID Connect client named my-client and configure it as a confidential client. For this REST API example, the application acts as an OAuth 2.0 Resource Server: it does not authenticate users itself and does not need the Keycloak Java adapter.
For interactive user authentication, prefer the Authorization Code flow with PKCE. The old Resource Owner Password Credentials flow should not be used for new applications. If you are integrating a machine-to-machine client instead, use Client Credentials and authorize the client rather than impersonating a user.
This script will configure your realm, user, and role automatically, saving a lot of time for future deployments. Check that your Keycloak Realm and Client are up and running:
Step 2: Configuring the Spring Boot Application
Now let’s set up our Spring Boot application to use Keycloak for authentication and authorization.
The application uses Spring Security's OAuth 2.0 Resource Server support. This is the recommended integration model for a REST API: Keycloak issues the access token, while Spring Security validates the JWT and applies the authorization rules.
2.1 Add Dependencies
Add the following dependencies to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
For Spring Boot 4, spring-boot-starter-webmvc is the preferred MVC starter. The older spring-boot-starter-web remains available for compatibility but is deprecated in favor of the more focused starter.
Make sure you’re using a current Spring Boot version as the parent:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/>
If you are maintaining a Spring Boot 3 application, use the latest supported 3.5.x maintenance release instead. Spring Boot 3.5.16 was the final open-source release of the 3.5.x generation, so new applications should generally consider Spring Boot 4.1.x when their dependency stack is ready for the migration.
Step 3: Coding the Spring Boot Application
Let’s create a basic REST API with secured endpoints.
3.1 Create the Controller
@RestController
@RequestMapping("/api")
public class ControllerHello {
@GetMapping("/hello")
public ResponseEntity<String> sayHello() {
return ResponseEntity.ok("Hello");
}
@GetMapping("/admin")
public ResponseEntity<String> sayHelloToAdmin() {
return ResponseEntity.ok("Hello Admin");
}
@GetMapping("/user")
public ResponseEntity<String> sayHelloToUser() {
return ResponseEntity.ok("Hello User");
}
}
This controller simply defines three endpoints: one public (/hello), and two secured (/admin and /user).
3.2 Set Up the Security Configuration
Here is the SecurityFilterChain which authorizes the REST endpoints:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
public static final String ADMIN = "admin";
public static final String USER = "user";
private final JwtConverter jwtConverter;
public SecurityConfig(JwtConverter jwtConverter) {
this.jwtConverter = jwtConverter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers(HttpMethod.GET, "/api/hello").permitAll()
.requestMatchers(HttpMethod.GET, "/api/admin/**").hasRole(ADMIN)
.requestMatchers(HttpMethod.GET, "/api/user/**").hasRole(USER)
.requestMatchers(HttpMethod.GET, "/api/admin-and-user/**")
.hasAnyRole(ADMIN, USER)
.anyRequest().authenticated()
)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt ->
jwt.jwtAuthenticationConverter(jwtConverter)
)
);
return http.build();
}
}
Spring Security's resource-server support validates the Bearer JWT and creates the authenticated security context. The custom JwtConverter is needed here because Keycloak places realm roles in the realm_access.roles claim, while Spring Security does not automatically turn those Keycloak-specific claims into ROLE_... authorities.
3.3 Create the Keycloak JWT Converter
The following converter maps Keycloak realm roles to Spring Security authorities:
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class JwtConverter implements Converter {
private final JwtAuthenticationConverter jwtAuthenticationConverter =
new JwtAuthenticationConverter();
@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
new KeycloakRealmRoleConverter()
);
return jwtAuthenticationConverter.convert(jwt);
}
static class KeycloakRealmRoleConverter
implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
Map<String, Object> realmAccess = jwt.getClaim("realm_access");
if (realmAccess == null) {
return Collections.emptyList();
}
Object roles = realmAccess.get("roles");
if (!(roles instanceof List<?> roleList)) {
return Collections.emptyList();
}
return roleList.stream()
.map(Object::toString)
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList();
}
}
}
This implementation converts a Keycloak realm role such as admin into the Spring Security authority ROLE_admin. Consequently, hasRole("admin") matches the role contained in the Keycloak access token.
If your application uses client roles rather than realm roles, adapt the converter to read the resource_access claim for your client. In larger enterprise applications, client roles are often preferable because they keep permissions scoped to a particular application rather than the entire realm.
Step 4: Configuring Keycloak Integration
Now configure your application.properties to point to the Keycloak server:
server.port=8180
spring.application.name=KeycloakSpringBootApplication
# OAuth 2.0 Resource Server
# Keycloak realm issuer used to validate JWT tokens.
spring.security.oauth2.resourceserver.jwt.issuer-uri=[http://localhost:8080/realms/ApplicationRealm](http://localhost:8080/realms/ApplicationRealm)
# Optional: explicitly configure the JWK Set endpoint.
# Normally issuer-uri is sufficient because Spring Security
# discovers the OpenID Connect metadata automatically.
# spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
# JWT Configuration
jwt.auth.converter.resource-id=my-client
jwt.auth.converter.principal-attribute=preferred_username
# Logging Configuration
logging.level.org.springframework.security=INFO
The most important setting is issuer-uri. Spring Boot and Spring Security use the OpenID Connect metadata published by Keycloak to discover the authorization server configuration and obtain the keys needed to verify JWT signatures.
You normally do not need to configure both issuer-uri and jwk-set-uri. Use issuer-uri for the standard configuration. Specify jwk-set-uri explicitly only when you have a particular deployment requirement, such as decoupling application startup from authorization-server discovery.
The principal-attribute property is application-specific. Common Keycloak claims include sub and preferred_username. Choose the claim that best represents the application's authenticated principal rather than assuming that an email address is always unique or immutable.
4.1 Environment-Specific Configuration
Do not hard-code the Keycloak URL in applications that move between development, staging, and production. Use environment variables or Spring configuration profiles:
spring.security.oauth2.resourceserver.jwt.issuer-uri=${KEYCLOAK_ISSUER_URI}
For example:
KEYCLOAK_ISSUER_URI=https://sso.example.com/realms/ApplicationRealm
This approach is particularly important for Kubernetes and OpenShift deployments, where the issuer URL normally comes from environment-specific configuration or deployment manifests.
Step 5: Testing the Application
5.1 Start Your Spring Boot Application
Use Maven to build and run the application:
mvn clean install
mvn spring-boot:run
The application will be available at http://localhost:8180.
5.2 Test the Public Endpoint
The public endpoint does not require a token:
curl http://localhost:8180/api/hello
The expected response is:
Hello
5.3 Authenticate and Call a Secured API
The original example used the OAuth 2.0 Resource Owner Password Credentials flow. This flow is not recommended for new applications because it requires the client application to collect the user's password. Instead, use the Authorization Code flow with PKCE for interactive users, or Client Credentials for service-to-service communication.
For a quick local test, you can use the Keycloak Admin Console to authenticate the user and obtain an access token, or use an OAuth 2.0-capable client such as Postman. Then send the access token to the API:
curl -H "Authorization: Bearer <ACCESS_TOKEN>" \
[http://localhost:8180/api/admin](http://localhost:8180/api/admin)
If the token contains the admin realm role and was issued for the expected realm, the endpoint returns:
Hello Admin
Calling the same endpoint without a valid Bearer token results in an authentication failure. A valid token without the required role results in an authorization failure.
5.4 Test the Token Claims
When troubleshooting authorization, inspect the decoded access token and verify that the expected claims are present. For this example, the token should contain a structure similar to:
{
"iss": "[http://localhost:8080/realms/ApplicationRealm](http://localhost:8080/realms/ApplicationRealm)",
"preferred_username": "user1",
"realm_access": {
"roles": [
"admin"
]
}
}
Never paste production access tokens into public JWT debugging websites. Access tokens can grant access to protected resources and should be treated as secrets.
Understanding the Spring Boot and Keycloak Security Flow
The complete request flow is straightforward:
- The user authenticates with Keycloak.
- Keycloak issues an OAuth 2.0 access token, normally a signed JWT for this resource-server configuration.
- The client sends the JWT to the Spring Boot API using the
Authorization: BearerHTTP header. - Spring Security validates the token issuer, signature, and relevant claims.
- The custom JWT converter maps Keycloak roles to Spring Security authorities.
- The
SecurityFilterChainevaluates the endpoint authorization rules. - The request reaches the controller only when authentication and authorization succeed.
This architecture keeps identity management in Keycloak while allowing each Spring Boot service to enforce its own resource-level authorization policies.
Production Considerations
The local Docker configuration is intentionally simple. A production Keycloak and Spring Boot deployment requires additional controls.
- Use HTTPS: Never expose production access tokens or credentials over unencrypted HTTP.
- Use a production Keycloak database: Do not use the development database configuration for production workloads.
- Protect client secrets: Store secrets in a dedicated secret-management solution rather than in Git repositories or application source code.
- Restrict the Keycloak management interface: Administrative endpoints should not be publicly accessible.
- Use short-lived access tokens: Configure token lifetimes according to the security requirements of the application.
- Validate the issuer: The
issuer-urishould identify the exact Keycloak realm trusted by the API. - Use least privilege: Give clients and users only the roles they actually require.
- Prefer client roles for application-specific permissions: This prevents a large shared set of realm roles from becoming an authorization boundary for unrelated applications.
- Monitor authentication failures: Centralize application and Keycloak logs and monitor unusual authentication and authorization patterns.
Keycloak and Spring Boot in Kubernetes and OpenShift
Keycloak 26 is designed for cloud-native deployments and provides container images and an operator for Kubernetes and OpenShift. A Spring Boot application can run independently as an OAuth 2.0 Resource Server and communicate with Keycloak through its internal or externally exposed issuer URL.
When deploying the application to Kubernetes or OpenShift, externalize the Keycloak issuer configuration:
env:
* name: KEYCLOAK_ISSUER_URI
value: "[https://sso.example.com/realms/ApplicationRealm](https://sso.example.com/realms/ApplicationRealm)"
Make sure the issuer contained in the JWT exactly matches the configured issuer expected by Spring Security. Reverse proxies and ingress controllers must therefore be configured consistently with the public Keycloak hostname.
For production deployments, also consider readiness and liveness probes, resource limits, TLS termination, centralized logging, metrics, distributed tracing, and high availability. Keycloak and the Spring Boot application have different operational responsibilities and should normally be scaled independently.
Common Keycloak and Spring Security Problems
401 Unauthorized
A 401 normally means that the request did not contain a valid Bearer token. Check the Authorization header, the token expiration time, the issuer, and the signature.
403 Forbidden
A 403 normally means that authentication succeeded but the authenticated principal does not have the authority required by the endpoint. Check the Keycloak role and the JWT converter.
Role is present in Keycloak but not recognized by Spring Security
Keycloak realm roles are commonly represented under realm_access.roles, while client roles are represented under resource_access. Spring Security's default JWT converter does not automatically map Keycloak's realm-role structure to ROLE_... authorities, which is why this example provides a custom converter.
Issuer validation fails
Check that spring.security.oauth2.resourceserver.jwt.issuer-uri exactly matches the iss claim in the access token. Differences in hostname, scheme, port, or realm name can cause validation to fail.
Application cannot start when Keycloak is unavailable
With standard issuer-uri discovery, Spring Security uses the authorization server metadata to configure JWT validation. In environments where application startup must be independent of Keycloak availability, explicitly configure the JWK Set URI and issuer validation according to the deployment requirements.
Conclusion
In this guide, you learned how to:
- Set up a Keycloak 26 server.
- Configure a Keycloak realm, client, user, and roles.
- Secure a Spring Boot 4 REST API using JWT tokens and OAuth 2.0.
- Use Spring Security's OAuth 2.0 Resource Server instead of the legacy Keycloak adapter.
- Map Keycloak roles to Spring Security authorities.
- Protect different API endpoints based on user roles.
- Prepare the application for environment-specific and cloud-native deployments.
Integrating Keycloak with Spring Boot provides a powerful and flexible way to manage authentication and authorization for your applications. Whether for internal tools or production-grade microservices, this setup can scale to meet enterprise security requirements while keeping identity management separate from the application itself.
For new applications, use standard OAuth 2.0 and OpenID Connect integration through Spring Security rather than relying on the legacy Keycloak Spring adapter. This keeps the application aligned with current Spring Security and Keycloak architecture and makes it easier to deploy the same security model across virtual machines, containers, Kubernetes, and OpenShift.
Source code: https://github.com/fmarchioni/mastertheboss/tree/master/spring/keycloak
Frequently Asked Questions
Does Spring Boot 4 support Keycloak 26?
Yes. Spring Boot 4 can integrate with Keycloak 26 using standard OAuth 2.0 and OpenID Connect support. For REST APIs, the recommended approach is to configure Spring Security as an OAuth 2.0 Resource Server that validates Keycloak-issued JWT access tokens.
Do I need the Keycloak Spring Boot adapter?
No. For a Spring Boot REST API, you generally do not need a Keycloak-specific Spring adapter. Spring Security's OAuth 2.0 Resource Server support can validate Keycloak JWTs using the realm issuer metadata and JWK Set.
Why does Spring Security not recognize my Keycloak roles?
Keycloak stores realm roles in the realm_access.roles claim and client roles in resource_access. Spring Security's default JWT authority mapping does not automatically understand these Keycloak-specific structures, so a custom JwtAuthenticationConverter is commonly required.
Should I use the password grant with Keycloak?
No. The Resource Owner Password Credentials flow requires an application to handle the user's password and is not recommended for new applications. Use Authorization Code with PKCE for interactive users and Client Credentials for machine-to-machine authentication.
Which versions should I use for a new Keycloak and Spring Boot application?
At the time of this update, Spring Boot 4.1.1 is the latest stable Spring Boot release and Keycloak 26.7.3 is the latest Keycloak maintenance release. Keep both projects on their current maintenance releases and apply security updates as they become available.
Recommended Articles
Connect Your Node.js App to Keycloak Using the Adapter - A Comprehensive Guide
Learn how to securely connect your Node.js app to Keycloak for authentication and authorization. Includes setup steps and example code.
Enable Two-Factor Authentication (2FA) with Keycloak Using Java
Learn how to integrate 2FA in Keycloak's standard authentication flow for enhanced security. #Keycloak #Java #Authentication #Security
Introduction to Keycloak (2026)
Introduction to Keycloak (2026): install and configure Keycloak, create your first Realm, Role, User and Client, and secure a WildFly application with OIDC — updated for Keycloak 26.7 and current WildFly releases.
Install Keycloak Client Adapters on WildFly for Enhanced Authentication/Authorization
Learn how to install Keycloak Client Adapters on WildFly 21 for seamless authentication/authorization management. #Keycloak #WildFly #Java