Fix Java Spring Boot Microsoft Entra ID Sign-In Errors
Why This Is Happening
I've seen this exact situation play out dozens of times: a Java developer spends an afternoon wiring up Java Spring Boot Microsoft Entra ID authentication, hits run, and instead of a clean login screen they get a blank OAuth2 redirect, a 401 Unauthorized from their own app, or , my personal favorite , the browser just sits there spinning while MSAL silently fails. It's maddening, especially when you're under deadline pressure.
The core problem is that Microsoft's identity platform for Java sits at the intersection of several moving parts: your Spring Boot app's security configuration, the Azure App Registration you created in the portal, the Microsoft Entra ID Spring Boot Starter client library, and the OpenID Connect protocol itself. Each of those layers has its own failure modes, and when they don't line up perfectly, the errors you get back are often spectacularly unhelpful. Something like AADSTS50011: The redirect URI specified in the request does not match or a cryptic Spring Security exception that buries the real cause six stack frames deep.
Here's the thing that trips up even experienced developers: Azure Java developer identity integration uses the OpenID Connect protocol to authenticate users. That means your app obtains an ID token from Microsoft Entra ID, not just a session cookie, and that token carries claims about the user's identity. If the token can't be issued, validated, or parsed by your Spring Boot starter, every request will bounce back unauthorized. The official Microsoft identity platform documentation covers this flow end to end, and most errors trace back to a handful of misconfigured values between your app's application.properties file and what's registered in the Azure portal.
Who runs into this most often? Primarily developers migrating from older username-password authentication to modern OAuth2/OIDC flows, or teams spinning up a new microservice and trying to hook it into an existing Microsoft Entra ID tenant. It also hits hard when someone copies a sample project from GitHub and doesn't realize the sample has hardcoded tenant IDs or redirect URIs that don't match their environment.
The error messages don't help because they're generated by the identity provider, not your app, and they deliberately omit detail for security reasons. AADSTS70011, AADSTS90009, AADSTS50055, each of these error codes means something specific, and I'll walk you through the ones that show up most frequently in real Java Spring Boot Microsoft Entra ID sign-in setups.
The good news: nearly every one of these problems has a clear, reproducible fix. The official Microsoft identity platform documentation is accurate and thorough, the trick is knowing which section to look at for your specific symptom, and understanding how the Azure AD Spring Boot Starter bridges the gap between Spring Security and the token issuance flow.
Browse all Microsoft fix guides →The Quick Fix, Try This First
Before you go down any rabbit hole, run through this checklist. In my experience, about 70% of broken Java Spring Boot Microsoft Entra ID setups fail for one of two reasons: a wrong redirect URI in the Azure App Registration, or a mismatched tenant ID in application.properties. Both take under two minutes to fix.
Step 1, Verify your redirect URI in the Azure portal. Sign into the Azure portal, navigate to Microsoft Entra ID → App Registrations, and open your app. Click Authentication in the left menu. Under "Web" → "Redirect URIs", confirm the exact value is:
http://localhost:8080/login/oauth2/code/
That trailing slash matters. The Microsoft Entra ID Spring Boot Starter constructs the redirect URI with that exact path and slash by default. If the value in the portal doesn't match character-for-character, you'll hit AADSTS50011 every single time.
Step 2, Check your tenant ID and client ID values. Open your src/main/resources/application.properties (or application.yml) and compare these values against what the Azure portal shows on the App Registration overview page:
spring.cloud.azure.active-directory.profile.tenant-id=YOUR_TENANT_ID
spring.cloud.azure.active-directory.credential.client-id=YOUR_CLIENT_ID
spring.cloud.azure.active-directory.credential.client-secret=YOUR_CLIENT_SECRET
Copy the Tenant ID and Application (client) ID directly from the portal overview, don't type them by hand. One transposed character breaks everything and the error message won't tell you which value is wrong.
Step 3, Confirm your JDK version. The Microsoft identity platform samples and the Spring Boot Starter are tested against JDK 17. If you're running JDK 11 or JDK 21, you may hit TLS compatibility or library version conflicts. Run java -version and make sure your output shows at least Java 17.
If your redirect URI, tenant ID, client ID, and JDK version all check out and the app still won't authenticate, keep reading, the step-by-step section covers the deeper issues.
This is where most Azure Java developer identity setups go wrong from the start. A single wrong selection during app registration causes failures that look like Spring Boot bugs but are actually portal configuration mismatches.
Sign into the Azure portal, click the top-left menu, and navigate to Microsoft Entra ID. In the left navigation pane, select App Registrations, then click New registration.
On the registration form, fill in the following exactly:
- Name: Something descriptive, like
java-spring-webapp-auth. This is display-only but keep it meaningful for your team. - Supported account types: Select "Accounts in this organizational directory only", this is the single-tenant option. If your app is multi-tenant, this choice changes, but for most enterprise Java apps you want single tenant.
- Redirect URI: Choose "Web" from the dropdown, then enter
http://localhost:8080/login/oauth2/code/exactly as written.
Click Register. Once the registration is created, you land on the overview page. From here, copy two values into a text file, you'll need them shortly:
- Application (client) ID
- Directory (tenant) ID
Then click Certificates & secrets in the left menu, choose Client secrets, and click New client secret. Give it a description, set the expiry period (12 or 24 months is typical), and click Add. Copy the secret Value immediately, once you navigate away from this page, the full secret is hidden forever and you'll have to create a new one.
If it worked: you should now have three strings, a client ID (GUID format), a tenant ID (GUID format), and a client secret (a long alphanumeric string). These three values go directly into your Spring Boot configuration. If you see any errors in the portal during registration, check that your Azure account has the Application Administrator or Global Administrator role on the Entra ID tenant.
Now let's get the right dependency into your Maven project. The Microsoft Entra ID Spring Boot Starter is the official Microsoft library that bridges Spring Security and the OpenID Connect sign-in flow, it handles token acquisition, validation, and session management so you don't have to wire all that up yourself.
Open your pom.xml and add the Azure Spring Boot Bill of Materials (BOM) and the Entra ID starter dependency:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-dependencies</artifactId>
<version>5.19.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Note: spring-boot-starter-oauth2-client is a required companion, the Entra ID starter builds on top of Spring Security's OAuth2 client support. If you omit it, the auto-configuration won't activate and you'll get a confusing NoSuchBeanDefinitionException at startup instead of a clear missing-dependency error.
Run mvn dependency:tree after updating the POM to confirm both spring-cloud-azure-starter-active-directory and msal4j (the Microsoft Authentication Library for Java) appear in the dependency graph. If msal4j is missing, your version of the starter BOM may be outdated.
After the dependency resolves cleanly, you should be able to run mvn spring-boot:run without compilation errors before you've even added the configuration properties.
This step is where the majority of MSAL for Java token acquisition errors originate. The property names are long and the documentation shows slightly different naming across versions, which leads to silent misconfiguration, the app starts fine but authentication simply never completes.
Open src/main/resources/application.properties and add the following block, substituting your actual values from Step 1:
# Microsoft Entra ID configuration
spring.cloud.azure.active-directory.enabled=true
spring.cloud.azure.active-directory.profile.tenant-id=<YOUR_TENANT_ID>
spring.cloud.azure.active-directory.credential.client-id=<YOUR_CLIENT_ID>
spring.cloud.azure.active-directory.credential.client-secret=<YOUR_CLIENT_SECRET>
# Optional but recommended: explicitly set the redirect URI
spring.cloud.azure.active-directory.redirect-uri-template=http://localhost:8080/login/oauth2/code/
A few things I want to flag from real-world Java Spring Boot OIDC login not working reports:
- The property key prefix changed between older versions of the library. Older guides use
azure.activedirectory.*. Current versions usespring.cloud.azure.active-directory.*. If your app was copied from an older tutorial, this mismatch will silently break things because Spring Boot won't throw an error on unrecognized properties, it just ignores them. - Never commit your
client-secretto source control. Use environment variables or Azure Key Vault. Set the property as:spring.cloud.azure.active-directory.credential.client-secret=${AZURE_CLIENT_SECRET}and exportAZURE_CLIENT_SECRETas an environment variable before starting the app. - The
spring.cloud.azure.active-directory.enabled=trueline is not optional, without it, the auto-configuration class won't activate on some versions of the starter.
Once properties are set, start the app with mvn spring-boot:run and navigate to http://localhost:8080. If configuration is correct, Spring Security will immediately redirect you to the Microsoft login page. A failure at this point means either the tenant ID is wrong (you'll get an AADSTS error in the browser) or the Spring Security filter chain isn't picking up the OAuth2 client configuration.
Getting the redirect to the Microsoft login page is only half the battle. Once the user signs in and the Microsoft identity platform Java developer flow returns an ID token, your app needs to know which routes require authentication and what to do with the token claims.
Create a security configuration class:
import com.azure.spring.cloud.autoconfigure.aad.AadWebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends AadWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated();
}
}
Extending AadWebSecurityConfigurerAdapter (provided by the Entra ID Spring Boot Starter) is critical. If you extend the generic Spring Security WebSecurityConfigurerAdapter instead, you lose the OIDC logout flow and ID token validation that the Entra ID adapter sets up automatically. This is a subtle bug, your app will accept the login redirect and token, but logout won't properly invalidate the Entra ID session, leaving users logged in on the Microsoft side even after clicking your app's logout button.
Once the user authenticates, you can access their details from the security context:
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import java.security.Principal;
@GetMapping("/profile")
public String profile(Model model, Principal principal) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
model.addAttribute("userName", token.getPrincipal().getAttribute("name"));
model.addAttribute("userEmail", token.getPrincipal().getAttribute("preferred_username"));
return "profile";
}
If this view returns null values for name or preferred_username, it means the ID token claims aren't being passed through. Check that the OpenID Connect scope includes profile, add spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read to your properties if you need richer user profile data via Microsoft Graph.
If your app needs to authenticate customers (not just internal employees) using Azure AD B2C Java authentication setup, the configuration is meaningfully different from standard Entra ID. I see a lot of developers copy a standard Entra ID config and wonder why B2C sign-in user flows don't work, these are separate services with different endpoints and property keys.
For Azure AD B2C, your application.properties needs to reference the B2C tenant and the specific user flow or custom policy name:
spring.cloud.azure.active-directory.b2c.enabled=true
spring.cloud.azure.active-directory.b2c.base-uri=https://<YOUR_B2C_TENANT>.b2clogin.com/<YOUR_B2C_TENANT>.onmicrosoft.com/
spring.cloud.azure.active-directory.b2c.credential.client-id=<YOUR_B2C_CLIENT_ID>
spring.cloud.azure.active-directory.b2c.credential.client-secret=<YOUR_B2C_CLIENT_SECRET>
spring.cloud.azure.active-directory.b2c.user-flows.sign-up-or-sign-in=B2C_1_signupsignin
spring.cloud.azure.active-directory.b2c.login-flow=sign-up-or-sign-in
Note that B2C uses a completely separate App Registration, you cannot reuse the same registration you created in Step 1 for both standard Entra ID and B2C. They live in different tenant types.
B2C also lets you integrate external social identity providers like Google, Facebook, or Apple. These are configured entirely in the Azure portal under your B2C tenant's Identity Providers blade, you don't need to change any Java code for the social provider connections themselves. The Spring Boot app just sends the user to the B2C login page and B2C handles brokering the social login, returning a B2C-issued token to your app. This is one of the design wins of the Microsoft identity platform for Java developers, your application code stays the same regardless of whether the end user authenticated with their Microsoft account, Google, or a local username-password flow.
If B2C login returns a generic AADB2C error code (they look like AADB2C90006, AADB2C90208), check the B2C user flow run page in the Azure portal, it provides a test interface that shows exactly what token would be issued and surfaces configuration errors directly.
Advanced Troubleshooting
If the five steps above didn't resolve your issue, you're likely dealing with an environment-specific or enterprise configuration problem. Here's how I approach the harder cases.
Reading AADSTS Error Codes
Every failed Azure Java developer identity authentication attempt ends with the browser landing on a Microsoft error page with an AADSTS code. These codes are precise and directly map to a problem category. Here are the ones I see most often in Java app setups:
- AADSTS50011: Redirect URI mismatch. The URI your app sent in the authorization request doesn't exactly match one registered in the portal. Go back to App Registration → Authentication and add the exact URI your app is using.
- AADSTS70011: Invalid scope. Your app is requesting an OAuth2 scope that isn't configured or consented. Check your
application.propertiesscope values. - AADSTS65001: User or admin consent required. The application hasn't been granted permission to access the requested resource. An admin needs to grant consent via the App Registration → API Permissions blade.
- AADSTS90009: The application is requesting a token on behalf of itself. This usually means your app is misconfigured to use a client credentials flow when it should be using an authorization code flow.
Enabling Debug Logging
Add this to application.properties to see the full OAuth2 request and response cycle in your Spring Boot logs:
logging.level.org.springframework.security=DEBUG
logging.level.com.azure.spring=DEBUG
logging.level.com.microsoft.aad.msal4j=DEBUG
The MSAL4J debug output shows the exact HTTP request being sent to the token endpoint and the raw response, this tells you exactly which parameter is wrong without you having to guess.
Network-Level Issues and Firewalls
In enterprise environments, your app server may sit behind a corporate proxy that intercepts HTTPS traffic to login.microsoftonline.com. The MSAL4J HTTP client uses the JVM's default proxy settings, so configure them at startup:
export JAVA_OPTS="-Dhttps.proxyHost=your.proxy.host -Dhttps.proxyPort=8080"
mvn spring-boot:run
If your proxy performs SSL inspection, you also need to import the proxy's root certificate into the JVM truststore using keytool, otherwise MSAL4J will throw a certificate chain validation error that looks like a network timeout.
Multi-Tenant and Domain-Joined Scenarios
If you selected "Accounts in any organizational directory" during app registration (multi-tenant) but your application.properties has a specific tenant ID, the token validation will fail for users from other tenants. For multi-tenant apps, set the tenant to common:
spring.cloud.azure.active-directory.profile.tenant-id=common
For domain-joined development machines in an Active Directory environment, make sure the account you're testing with has a user object in your Entra ID tenant. Local AD accounts that aren't synced to Entra ID via Azure AD Connect cannot sign in to Entra ID, this is a common source of confusion when testing on a corporate-joined laptop.
Conditional Access Policy Blocks
If users can sign in from some networks or devices but not others, Conditional Access policies in Entra ID are almost certainly the cause. An admin needs to check the Entra ID → Security → Conditional Access blade and review which policies apply to your app registration. Your Java app itself cannot work around Conditional Access, it's enforced at the identity provider level by design.
Prevention & Best Practices
Once your Java Spring Boot Microsoft Entra ID integration is working, a few proactive habits will keep it working, and stop you from waking up at 2am to a production incident because a client secret expired.
Set up client secret expiry alerts. Client secrets in Entra ID have a maximum lifetime of 24 months. When they expire, your app's token acquisition fails completely and users get locked out. Go to App Registration → Certificates & secrets, note the expiry date of every secret, and set a calendar reminder 30 days before it expires. Better yet, use certificate-based authentication instead of client secrets for production apps, certificates are more secure and can have longer validity periods.
Use environment variables or Azure Key Vault for secrets. Your client-secret should never live in a properties file that gets committed to source control. Spring Boot's @Value("${AZURE_CLIENT_SECRET}") pattern and environment variable injection is the minimum bar. For production workloads, integrate Azure Key Vault using Spring Cloud Azure Key Vault Starter so secrets are pulled at runtime and automatically rotated.
Test sign-in and sign-out flows after every dependency update. When you update the Spring Cloud Azure BOM version or the Spring Boot parent POM, the OAuth2 client behavior can change subtly. Run through a full authentication cycle, sign in, access a protected route, and sign out, before merging any dependency update to main.
Monitor the Entra ID sign-in logs. The Azure portal's Microsoft Entra ID → Monitoring → Sign-in logs shows every authentication attempt against your app registration, including failures. Set up a diagnostic setting to stream these logs to a Log Analytics workspace so you can alert on unusual failure patterns before users start filing support tickets.
- Pin your
spring-cloud-azure-dependenciesBOM version explicitly inpom.xml, never useLATESTorRELEASEfor identity-related dependencies in production. - Register separate app registrations for dev, staging, and production environments, sharing one registration across environments creates redirect URI conflicts and makes secrets management a nightmare.
- Enable "Access token" and "ID token" under the app registration's Authentication → Implicit grant section only if you're building a single-page app that calls APIs directly, for server-side Spring Boot apps, you do not want these checked.
- Use jwt.ms during development to decode and inspect every token your app receives, it's the fastest way to verify that the claims your app depends on (roles, groups, email) are actually present in the token before you write filtering logic around them.
Frequently Asked Questions
Why does my Java Spring Boot app redirect to login but then just loop back to the login page?
This redirect loop almost always means the authorization code exchange is failing silently. The most common cause is a wrong or expired client secret, the app receives the authorization code from Entra ID, tries to exchange it for an ID token using the client secret, fails, and redirects the user to login again. Check your application log for a MSAL4J exception, verify your client-secret value in application.properties matches exactly what's shown in the Azure portal (remember you only see the full value once, right after creation), and confirm the secret hasn't passed its expiry date in App Registration → Certificates & secrets.
I get AADSTS50011 even though my redirect URI looks correct, what am I missing?
AADSTS50011 is almost always a character-level mismatch. Check three things: trailing slashes (the default Spring Boot starter URI requires a trailing slash: /login/oauth2/code/), HTTP vs HTTPS (for localhost development it must be http://, but production needs https:// and they must both be registered in the portal), and port number (if you changed your app's server port away from 8080, the registered URI still says 8080). The Azure portal also performs case-sensitive matching on the path component.
How do I access Microsoft Graph API data from my Java Spring Boot app after sign-in?
After a user signs in, your app holds an ID token (which proves identity) but you need a separate access token scoped to https://graph.microsoft.com/User.Read to call Microsoft Graph. Add spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read to your properties, then inject OAuth2AuthorizedClientService in your controller to retrieve the access token. Use that token as a Bearer header in an HTTP call to https://graph.microsoft.com/v1.0/me. The official docs cover this in the "Access user details" section of the Java Spring Boot guide.
Can I use this same setup for role-based access control in my Spring Boot app?
Yes, and it's well supported. You assign App Roles to your users in the App Registration → App roles blade, then the roles claim appears in the ID token automatically. On the Spring Boot side, annotate your controller methods with @PreAuthorize("hasAuthority('APPROLE_YourRoleName')") and Spring Security handles the role check against the token claims. The @EnableGlobalMethodSecurity(prePostEnabled = true) annotation on your security config class is required for these method-level annotations to activate. Make sure to also enable spring.cloud.azure.active-directory.user-group.use-transitive-members=true if your role assignments use nested group membership.
My app works on localhost but breaks when deployed to Azure Container Apps or App Service, why?
Two things change when you move off localhost. First, your redirect URI changes from http://localhost:8080/... to your production URL, you need to add the production redirect URI to your App Registration in the Azure portal (Authentication → Add URI). Second, your client-secret is probably still hardcoded in application.properties and not being picked up from the deployment environment. Set it as an Application Setting (environment variable) in the App Service or Container App configuration blade, and reference it via ${AZURE_CLIENT_SECRET} in your properties file. Also make sure you're running JDK 17 in your container image, some base images default to JDK 11.
What's the difference between using MSAL4J directly versus the Microsoft Entra ID Spring Boot Starter?
MSAL4J is the low-level Microsoft Authentication Library for Java, it handles the actual token acquisition protocol and gives you fine-grained control over every aspect of the auth flow. The Microsoft Entra ID Spring Boot Starter wraps MSAL4J and integrates it with Spring Security's existing OAuth2 client infrastructure, so you get auto-configuration, property-based setup, and seamless integration with Spring Security annotations. For most Spring Boot apps, the starter is the right choice, it dramatically reduces the amount of code you write and maintain. Use MSAL4J directly only when you need to integrate authentication into a non-Spring Java app (like a Tomcat, JBoss EAP, WebLogic, or WebSphere deployment) or when you need flow control that the starter doesn't expose through configuration properties.