Thursday, October 15, 2026

Spring Boot vs .NET Core: The Ultimate Comparison for Developers and Solution Architects

As developers progress toward Solution Architect roles, one question appears repeatedly:

Should I use Spring Boot or ASP.NET Core?

The reality is that both frameworks are world-class enterprise platforms capable of building APIs, microservices, cloud-native applications, and large-scale enterprise systems.

The real question isn't which is better. The question is:

Which platform solves your problem more effectively within your technology ecosystem?

Executive Summary

  • Spring Boot dominates the Java enterprise ecosystem.
  • .NET Core dominates Microsoft's cloud ecosystem.
  • Both are excellent for APIs and microservices.
  • Spring Boot shines in multi-cloud environments.
  • .NET Core shines within Azure environments.

Technology Stack

Spring Boot

Language: Java
Vendor: VMware / Broadcom + Community
Framework: Spring Framework
Runtime: JVM

.NET Core

Language: C#
Vendor: Microsoft
Framework: ASP.NET Core
Runtime: .NET Runtime

Developer Philosophy

Spring Boot

Spring's philosophy is:

Everything is a Bean

Applications revolve around:

  • Beans
  • Dependency Injection
  • Application Context
  • Component Scanning

.NET Core

.NET's philosophy is:

Everything is a Service

Applications revolve around:

  • Dependency Injection Container
  • Services
  • Configuration
  • Middleware

Hello World API Comparison

Spring Boot

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public String getUser(
            @PathVariable Long id) {

        return "User " + id;
    }
}

Request:

GET /users/1

Response:

User 1

.NET Core

[ApiController]
[Route("users")]
public class UserController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetUser(long id)
    {
        return Ok($"User {id}");
    }
}

Request:

GET /users/1

Response:

User 1

Dependency Injection

Spring Boot

@Service
public class UserService {
}

@Autowired
private UserService service;

.NET Core

builder.Services.AddScoped<UserService>();

public UserController(UserService service)
{
    _service = service;
}
Winner: .NET Core
Cleaner syntax and constructor injection is more commonly used.

Configuration Management

Spring Boot

application.yml

server:
  port: 8080

my:
  api:
    url: google.com
@Value("${my.api.url}")
private String url;

.NET Core

appsettings.json

{
  "MyApi": {
    "Url": "google.com"
  }
}
configuration["MyApi:Url"]
Winner: .NET Core

JWT Authentication

Since modern APIs commonly use JWT authentication, let's compare implementations.

Spring Boot

Jwt jwt =
    jwtDecoder.decode(token);
Or:
.oauth2ResourceServer(
    oauth2 -> oauth2.jwt()
)

.NET Core

builder.Services
    .AddAuthentication(
        JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer();

Out of the box:

  • Signature Validation
  • Issuer Validation
  • Audience Validation
  • Expiry Validation
Winner: .NET Core

Database Access

Spring Boot

@Repository
public interface UserRepository
        extends JpaRepository<User, Long> {
}
Uses:
JPA
Hibernate

.NET Core

public class AppDbContext
    : DbContext
{
}
Uses:
Entity Framework Core

Query Example

Spring Boot

User user =
    repository.findByEmail(email);

.NET Core

User user =
    context.Users
        .FirstOrDefault(
            u => u.Email == email);
Winner: .NET Core (thanks to LINQ)

Logging

Spring Boot

private static final Logger logger =
    LoggerFactory.getLogger(
        UserService.class);

.NET Core

private readonly ILogger<UserService>
    _logger;
Winner: .NET Core

Asynchronous Programming

Spring Boot

CompletableFuture<User>

.NET Core

public async Task<User>
GetUserAsync()
{
}

The async/await model in .NET is easier to understand and maintain.

Winner: .NET Core

Microservices

Spring Ecosystem

Spring Boot
Spring Cloud
Feign
Kafka
Eureka
Resilience4j

.NET Ecosystem

ASP.NET Core
Dapr
Polly
Azure Service Bus
Health Checks
Azure SDKs
Winner: Tie

Azure Integration

This is where .NET Core shines.

Azure Function Example

var credential =
    new DefaultAzureCredential();

var client =
    new SecretClient(
        vaultUri,
        credential);

Microsoft SDKs are generally released first and best integrated with .NET.

Winner: .NET Core

Performance

Both frameworks are extremely fast.

However, ASP.NET Core consistently ranks among the fastest web frameworks available.

Winner: .NET Core

Enterprise Adoption

Spring Boot Dominates In

  • Banking
  • Insurance
  • Telecommunications
  • Kubernetes
  • OpenShift
  • Multi-cloud environments

.NET Core Dominates In

  • Azure-first organizations
  • Government departments
  • Microsoft-heavy enterprises
  • M365 integrations
  • Entra ID environments

Real Example: JWT Validation

Spring Boot Approach

Jwt jwt =
    jwtDecoder.decode(token);

String appId =
    (String) jwt.getClaim("appid");

.NET Core Approach

string appId =
    User.Claims
        .First(c => c.Type == "appid")
        .Value;

Both achieve the same result.


Architect Perspective

Choose Spring Boot When

  • You are a Java-first organization.
  • You need platform independence.
  • You run on Kubernetes, OpenShift, AKS, EKS, or GKE.
  • Your engineering teams are predominantly Java developers.

Choose .NET Core When

  • You are heavily invested in Azure.
  • You integrate with APIM.
  • You use Azure Functions.
  • You use Microsoft Graph.
  • You use Entra ID.
  • You rely on Azure SDKs.



Feature-by-Feature Scorecard

Area Spring Boot .NET Core Winner
Learning Curve Moderate Easier .NET Core
Dependency Injection Excellent Excellent .NET Core
Azure Integration Good Outstanding .NET Core
Kubernetes Ecosystem Outstanding Excellent Spring Boot
Performance Excellent Outstanding .NET Core
Enterprise Adoption Outstanding Outstanding Tie
Cloud Native Development Excellent Excellent Tie
Security Excellent Excellent Tie
Productivity High Very High .NET Core
Open Source Ecosystem Massive Large Spring Boot

Real-World Enterprise Examples

Scenario 1 - Banking Platform

A large bank running hundreds of microservices on Kubernetes may choose Spring Boot because of its mature ecosystem, deep integration with Kafka, Spring Cloud, and strong support for container platforms such as OpenShift and Kubernetes.

Customer Portal
       ↓
Spring Boot APIs
       ↓
Kafka
       ↓
Spring Microservices
       ↓
Oracle Database

Scenario 2 - Azure Enterprise Platform

An organisation heavily invested in Azure, APIM, Azure Functions, Microsoft Graph, Entra ID, Service Bus, and Azure DevOps may choose .NET Core.

Client Application
        ↓
Azure API Management
        ↓
ASP.NET Core APIs
        ↓
Azure Service Bus
        ↓
Azure SQL

Scenario 3 - Government Platforms

Government agencies often use either platform depending on historical investments. It is increasingly common to find both technologies coexisting within the same organisation.

Citizen Portal
      ↓
APIM
      ↓
.NET APIs
      ↓
Java Services
      ↓
Enterprise Systems

Career Perspective

For developers, the choice of framework is often influenced by the technology stack used in their organisation.

For Solution Architects, the choice is driven by architecture, scalability, security, operational maturity, team capability, and cloud strategy.

A modern Solution Architect should be capable of understanding both ecosystems because many large enterprises operate hybrid environments consisting of:

  • Java/Spring Boot Services
  • .NET Core Services
  • Azure Functions
  • API Management Gateways
  • Kubernetes Platforms
  • Microsoft Entra ID Security

Lessons from My Own Journey

While building Azure Functions, integrating APIM, validating JWT tokens, decoding claims, connecting to Microsoft Graph, configuring Managed Identities, and working with Entra ID, I discovered an important architectural truth:

The business problem matters far more than the framework.

Whether the implementation is Java Spring Boot or ASP.NET Core, the architectural principles remain identical:

  • Authentication
  • Authorization
  • Scalability
  • Observability
  • Resilience
  • Performance
  • Maintainability
  • Security

The best architects understand patterns first and technologies second.


Final Thoughts

After working deeply with both ecosystems, my conclusion is simple:

Choose Spring Boot when your organisation is Java-centric, cloud-agnostic, Kubernetes-driven, and heavily invested in the Spring ecosystem.

Choose .NET Core when your organisation is Azure-centric, deeply integrated with Microsoft technologies, and looking for maximum developer productivity.

For developers aspiring to become Solution Architects, learning both platforms provides tremendous value because the architectural concepts transfer directly between them.

Spring Boot teaches enterprise engineering.

.NET Core teaches cloud-native productivity.

Mastering both teaches architecture.

Thank you for reading. If you're currently exploring Azure, APIM, Azure Functions, Microsoft Graph, Entra ID, Spring Boot, or .NET Core architecture patterns, feel free to share your experiences and lessons learned.



Azure Functions

Azure Functions: Complete Guide for Developers and Solution Architects

Azure Functions is one of Microsoft's most powerful serverless offerings. It enables developers to run code on demand without worrying about infrastructure, servers, operating systems, patching, or scaling.

In this article, I summarize everything I learned about Azure Functions while building and deploying Java-based functions integrated with Azure API Management (APIM), Microsoft Entra ID, and Microsoft Graph.

What is Azure Function?

Azure Functions is a serverless compute service that allows developers to execute code in response to events without managing infrastructure.

Microsoft automatically handles:

  • Server provisioning
  • Operating system management
  • Patching
  • Scaling
  • Availability
  • Monitoring

Developers can focus entirely on writing business logic.

Traditional Application

Application
     ↓
Virtual Machine
     ↓
Operating System
     ↓
Infrastructure

Managed by Developer
Azure Function

Application
     ↓
Azure Functions Platform

Managed by Azure

Why Use Azure Functions?

1. Serverless Computing

No virtual machines need to be managed. Developers deploy code and Azure executes it automatically.

2. Automatic Scaling

10 Requests
      ↓
1 Function Instance

1000 Requests
      ↓
Multiple Function Instances

Scaling happens automatically.

3. Pay for What You Use

On Consumption and Flex Consumption plans:

  • No requests = No cost
  • Pay only when code executes

4. Event Driven Architecture

Functions execute when specific events occur.

Event
   ↓
Azure Function
   ↓
Business Logic

Common Azure Function Use Cases

  • REST APIs
  • Graph API integrations
  • File processing
  • Scheduled jobs
  • Background processing
  • Data transformations
  • Service Bus consumers
  • Microservice components
  • Webhook endpoints
  • IoT processing

Types of Azure Function Triggers

Every Azure Function starts with a trigger. A trigger defines what causes the Function to execute.

1. HTTP Trigger

Most commonly used trigger.

Browser
   ↓
HTTP Request
   ↓
Azure Function

Java Example:

@HttpTrigger(
    name = "req",
    methods = {HttpMethod.GET, HttpMethod.POST},
    authLevel = AuthorizationLevel.ANONYMOUS
)

Use Cases:

  • REST APIs
  • Webhook endpoints
  • Authentication services
  • Microservices

2. Timer Trigger

Schedule
   ↓
Azure Function

Example:

Every Day 2 AM

Use Cases:

  • Nightly processing
  • Reports
  • Cleanup jobs
  • Data synchronization

3. Blob Trigger

Blob Uploaded
      ↓
Azure Function

Use Cases:

  • Image processing
  • PDF processing
  • File validation

4. Queue Trigger

Storage Queue
       ↓
Azure Function

Use Cases:

  • Asynchronous processing
  • Decoupled systems

5. Service Bus Trigger

Azure Service Bus
         ↓
Azure Function

Use Cases:

  • Enterprise integrations
  • Microservices
  • Event driven systems

6. Event Grid Trigger

Resource Event
      ↓
Event Grid
      ↓
Azure Function

7. Cosmos DB Trigger

Document Updated
        ↓
Azure Function

8. Event Hub Trigger

IoT Events
     ↓
Event Hub
     ↓
Azure Function

Azure Function Hosting Plans

Consumption Plan

Classic serverless model.

No Requests
      ↓
Scale to Zero

Flex Consumption Plan

New generation serverless hosting plan.

Benefits:

  • Faster startup times
  • Improved networking support
  • Enhanced scaling

Premium Plan

For enterprise workloads.

Benefits:

  • No cold starts
  • VNET Integration
  • Higher throughput

App Service Plan

Dedicated infrastructure.

App Service Plan
      ↓
Azure Function

Ways to Develop Azure Functions

Visual Studio Code

Most popular choice.

Extensions:

  • Azure Functions Extension
  • Azure Tools Extension

Visual Studio

Best for .NET Developers.

IntelliJ IDEA

Popular among Java developers.

Maven

Common approach for Java Azure Functions.

mvn azure-functions:run

Ways to Deploy Azure Functions

1. Azure Portal

Manual deployment through Azure Portal. Useful for experiments and proofs of concept.

2. VS Code Deployment

Right Click Function App
       ↓
Deploy

3. Maven Deployment

mvn azure-functions:deploy

Preferred deployment method for Java projects.

4. ZIP Deployment

az functionapp deployment source config-zip

Useful for scripted deployments.

5. Azure CLI Deployment

az functionapp create
az functionapp deploy

6. GitHub Actions

GitHub
    ↓
Build
    ↓
Deploy
    ↓
Azure Function

7. Azure DevOps Pipelines

Build Pipeline
      ↓
Release Pipeline
      ↓
Azure Function

8. Terraform

Infrastructure as Code deployment.

Ways to Test Azure Functions Locally

1. Azure Functions Core Tools

func start
or
mvn azure-functions:run

2. Browser Testing

For GET requests:

http://localhost:7071/api/HttpExample?name=Vikas

3. Curl

curl "http://localhost:7071/api/HttpExample?name=Vikas"

4. Postman

Most common testing tool.

5. Bruno

Lightweight API testing tool.

6. VS Code Debugger

Supports breakpoints and step-by-step debugging.

7. Unit Testing

JUnit
Mockito

Useful for testing business logic.

My Java Azure Function Example

I built and deployed the following Java Azure Function.

@FunctionName("HttpExample")
public HttpResponseMessage run(
    @HttpTrigger(
        name = "req",
        methods = {HttpMethod.GET, HttpMethod.POST},
        authLevel = AuthorizationLevel.ANONYMOUS)
        HttpRequestMessage<Optional<String>> request,
    final ExecutionContext context) {

    final String query =
        request.getQueryParameters().get("name");

    final String name =
        request.getBody().orElse(query);

    if (name == null) {
        return request.createResponseBuilder(
            HttpStatus.BAD_REQUEST)
            .body("Please pass a name")
            .build();
    }

    return request.createResponseBuilder(
        HttpStatus.OK)
        .body("Hello, " + name)
        .build();
}

Testing Through Browser

Once deployed, the Function can be called through a browser.

https://myfunction.azurewebsites.net/api/HttpExample?name=Vikas

Response:

Hello, Vikas

Calling Azure Function Using Postman

GET Request Example

Method:

GET

URL:

https://myfunction.azurewebsites.net/api/HttpExample?name=Vikas

Response:

Hello, Vikas

POST Request Example

Method:

POST

Headers:

Content-Type: text/plain

Body:

Vikas

Calling Azure Function Using Bearer Tokens

I also built a Function that accepts Authorization headers.

Authorization: Bearer eyJ...

The Function:

  • Receives the JWT token
  • Decodes the token
  • Extracts the App ID claim
  • Calls Microsoft Graph

Calling Azure Function Through APIM

One interesting scenario I implemented was:

Client
   ↓
Azure API Management
   ↓
Azure Function
   ↓
Microsoft Graph

APIM policy:

<send-request
    mode="new"
    response-variable-name="userContext">

    <set-url>
        https://myfunction.azurewebsites.net/api/ValidateApp
    </set-url>

    <set-method>POST</set-method>

    <set-header
        name="Authorization"
        exists-action="override">

        <value>
            @(context.Request.Headers
              .GetValueOrDefault("Authorization"))
        </value>

    </set-header>

</send-request>

Key Benefits of Azure Functions

  • No infrastructure management
  • Serverless architecture
  • Automatic scaling
  • Rapid development
  • Low operational cost
  • Strong Azure integration
  • Easy API creation
  • Event-driven processing
  • Supports Java, .NET, Python, Node.js, and PowerShell

Key Learnings

  • Azure Functions are ideal for event-driven workloads.
  • Java Azure Functions are straightforward to develop and deploy.
  • Azure Functions integrate seamlessly with APIM.
  • Bearer tokens can be passed through APIM to Functions.
  • Microsoft Graph can be called from Functions for advanced authorization patterns.
  • Managed Identity is the preferred authentication mechanism for Azure services.
  • Functions are excellent building blocks for microservices and integration solutions.

Conclusion

Azure Functions provide a modern serverless platform for building APIs, integrations, automation, and event-driven workloads. Developers can focus on business logic while Azure manages infrastructure, scaling, availability, and operations.

Whether you are building REST APIs, integrating Microsoft Graph, processing files, or implementing enterprise authorization solutions behind APIM, Azure Functions provide a powerful and cost-effective way to deliver cloud-native applications.

ASP.NET Core MVC

Benefits over ASP.NET MVC:

1. Inbuilt support to Dependency Injection

2. Built on .NET Core framework which gives the ability that you don't need to install the .NET Framework to run it. Instead, you ship all the required dlls with your application.

3. Cross platform apps to be run on Windows/Linux/Mac

4. Usage of MVC and WebAPI in same app.

5. Support for Cloud-Based Development.


How to register dependencies:
1. AddTransient
2. AddSingleton
3. AddScoped


Entity Framework: (latest version 7 but know as EF Core)
1. Open Source
2. Usually Code First and Database -First approaches but EF Core is Code First only
3. It is an ORM




My Azure Container Journey: From Traditional VMs to Modern Container Apps

As part of my Azure architecture learning journey, I recently explored Azure Container Apps and evaluated whether it could be a suitable replacement for traditional VM-hosted Java applications.

Coming from a background where Spring Boot applications were deployed on Tomcat servers running inside Azure VMs, I wanted to understand the benefits, trade-offs, and migration path towards a more cloud-native architecture.

My Current State

Traditionally, the application architecture looked like this:

Azure VM
    |
    +-- Tomcat 11
            |
            +-- Spring Boot Application (WAR)

While this setup is stable and familiar, it requires managing:

  • Operating system patching
  • Tomcat upgrades
  • Security updates
  • Capacity planning
  • Infrastructure maintenance

The Question

Can I move away from VMs and still run my Spring Boot and Tomcat-based applications efficiently in Azure?

The answer is yes: Azure Container Apps.

What is Azure Container Apps?

Azure Container Apps is a fully managed container platform that allows developers to run containerized applications without managing Kubernetes clusters or underlying virtual machines.

It provides:

  • Automatic scaling
  • Managed infrastructure
  • Built-in ingress
  • Revision management
  • Integration with Azure Container Registry
  • Microservices-friendly deployments

Can Azure Container Apps Run Tomcat?

Absolutely.

This was one of the first questions I investigated.

Azure Container Apps does not care what technology stack is running inside the container. If a Docker image contains Apache Tomcat, Azure Container Apps can host it.

The deployment becomes:

Azure Container Apps
        |
        +-- Docker Container
                |
                +-- Tomcat 11
                        |
                        +-- Spring Boot WAR

Creating a Tomcat Container

A simple Dockerfile can be used:

FROM tomcat:11.0-jdk21

RUN rm -rf /usr/local/tomcat/webapps/*

COPY tap.war /usr/local/tomcat/webapps/ROOT.war

EXPOSE 8080

CMD ["catalina.sh", "run"]

This image packages both Tomcat and the application into a single deployable unit.

Container Apps vs App Service

While researching deployment options, I compared Azure App Service and Azure Container Apps.

Azure App Service

  • Simpler deployment experience
  • No Docker knowledge required
  • Great for traditional web applications
  • Lower operational complexity

Azure Container Apps

  • Cloud-native architecture
  • Container-based deployments
  • Better portability
  • Microservices ready
  • Future path towards Kubernetes
  • Fine-grained scaling

For simple lift-and-shift migrations, App Service is attractive. For long-term modernization and architectural flexibility, Container Apps stands out.

The Architecture I Envision

For the TAP platform, a future-state architecture could look like:

Angular Frontend
        |
        +-- Azure Static Web Apps
                    |
                    +-- Azure API Management
                                |
                                +-- Azure Container Apps
                                            |
                                            +-- Spring Boot API
                                                        |
                                                        +-- Database

This introduces clear separation of concerns while reducing operational overhead.

Lessons Learned Along the Way

  • Azure Container Apps can host any containerized application.
  • Tomcat applications can be containerized with minimal changes.
  • Spring Boot executable JARs are often more efficient than external Tomcat deployments.
  • API Management fits naturally in front of Container Apps.
  • Container Apps provides a strong modernization path without the complexity of AKS.

My Recommended Migration Strategy

Phase 1

VM
 |
 +-- Tomcat
         |
         +-- WAR

Move the existing application into a Tomcat container and deploy it to Azure Container Apps.

Phase 2

Container App
 |
 +-- Spring Boot Executable JAR

Eliminate standalone Tomcat and simplify operations further.

Phase 3

Static Web Apps
        |
        +-- APIM
                |
                +-- Container Apps

Adopt a fully cloud-native architecture that is secure, scalable, and easier to maintain.

Final Thoughts

My exploration of Azure Container Apps has reinforced an important principle of modern architecture: focus on the application and let the platform manage the infrastructure.

For organizations still running Java applications inside VMs, Azure Container Apps offers a compelling modernization path that balances simplicity, scalability, and operational efficiency.

The journey has only just begun, but Container Apps is quickly becoming one of my favourite Azure services.


Author: Vikas Kumar

Topics: Azure, Container Apps, Spring Boot, Tomcat, API Management, Cloud Architecture, Modernization

From Azure Functions to APIM and Microsoft Graph: A Hands-On Architecture Journey

October 2026

Today was one of those rewarding deep-dive sessions where theory met practice. The goal was simple: understand how Azure API Management (APIM), Azure Functions, Microsoft Entra ID tokens, and Microsoft Graph can work together in a real-world enterprise authorization scenario.

The Starting Point: Building My First Java Azure Function

I started by creating a Java Azure Function locally, deploying it to Azure, and verifying that it could be invoked through a browser.

@FunctionName("HttpExample")
public HttpResponseMessage run(...)

Successfully deploying and invoking this function validated:

  • Java Azure Functions runtime
  • Azure deployment process
  • HTTP triggers
  • Azure Function hosting
  • Public endpoint accessibility

Moving Back to APIM

The real objective was not the function itself. The objective was to build and understand a scalable enterprise authorization architecture.

Consumer
    ↓
APIM
    ↓
Azure Function
    ↓
Microsoft Graph
    ↓
Authorization Decision
    ↓
Backend API

This is a common enterprise pattern where APIM acts as a gateway and Azure Functions perform custom authorization or enrichment logic.

Calling Azure Function from APIM

I implemented an APIM policy using send-request.

<send-request mode="new"
              response-variable-name="userContext">

    <set-url>
        https://functionapp.azurewebsites.net/api/HttpExample
    </set-url>

    <set-method>POST</set-method>

</send-request>

This was my first successful proof that:

APIM
   ↓
Azure Function
   ↓
Response
   ↓
APIM

works seamlessly.

The Function response was captured inside:

context.Variables["userContext"]

Understanding APIM send-request

One of the key learnings was that APIM can act as an HTTP client itself.

The send-request policy allows APIM to invoke:

  • Azure Functions
  • REST APIs
  • Microsoft Graph
  • Internal Services
  • Validation Services

without forwarding the request to the backend.

Building a Validation Function

Next, I added a second Azure Function:

@FunctionName("ValidateApp")

The goal was:

  1. Receive a Bearer Token
  2. Decode JWT
  3. Extract Application ID
  4. Call Microsoft Graph
  5. Retrieve Application Name

Receiving Authorization Headers

Initially the function returned:

Authorization header missing

Investigation revealed something important.

authorization=Bearer eyJ...

instead of:

Authorization=Bearer eyJ...

This demonstrated a critical concept:

HTTP Header names are case-insensitive.

The safest implementation was:

String authHeader = request.getHeaders()
    .entrySet()
    .stream()
    .filter(h -> h.getKey().equalsIgnoreCase("Authorization"))
    .map(Map.Entry::getValue)
    .findFirst()
    .orElse(null);

Decoding JWT Tokens

I used:

DecodedJWT jwt = JWT.decode(token);

An important realization:

JWT.decode() does not validate the token. It simply decodes the payload and makes claims available.

Useful claims included:

appid
tid
aud
roles

Why APIM Should Validate Tokens

In an enterprise architecture, JWT validation should not happen inside every service. Instead:

Consumer
   ↓
APIM
   ↓
validate-jwt
   ↓
Azure Function

APIM validates:

  • Signature
  • Issuer
  • Audience
  • Expiration

Once APIM validates the token, downstream services can trust it.

Discovering the Calling Application

A client-credentials token contained:

{
  "appid":"4fa96e1a-2762-416b-b81e-bfea8b3e66de"
}

The Azure Function extracted it using:

String appId =
    jwt.getClaim("appid").asString();

with fallback support for:

azp

Microsoft Graph Integration

The next step was querying Microsoft Graph.

appid
   ↓
Microsoft Graph
   ↓
Service Principal
   ↓
Display Name

Implementation:

TokenCredential credential =
    new DefaultAzureCredentialBuilder().build();

GraphServiceClient graphClient =
    new GraphServiceClient(credential);

Managed Identity Learnings

The preferred enterprise approach is:

  • Enable Function App Managed Identity
  • Avoid client secrets
  • Use DefaultAzureCredential
  • Authenticate to Graph securely

Benefits include:

  • No secret management
  • Automatic credential rotation
  • Improved security posture
  • Native Azure integration

Required Graph Permissions

To query Service Principals, the Function Managed Identity requires:

  • Application.Read.All
  • or Directory.Read.All

with administrator consent granted.

APIM Debugging Techniques

One useful debugging technique was returning the Authorization header directly:

<return-response>
    <set-body>
        @(context.Request.Headers.GetValueOrDefault("Authorization"))
    </set-body>
</return-response>

This quickly proves whether APIM is receiving the token.

Liquid Templates vs APIM Expressions

Another interesting topic explored was:

template="liquid"

Liquid templates are useful for:

  • JSON Transformation
  • XML Transformation
  • SOAP to REST conversion
  • Payload reshaping

Whereas APIM expressions:

@(...)

are used for:

  • Variables
  • Headers
  • Request Context
  • Conditional Logic

The Emerging Architecture

Consumer
    ↓
APIM
    ↓
JWT Validation
    ↓
Azure Function
    ↓
Extract appid
    ↓
Microsoft Graph
    ↓
Lookup Service Principal
    ↓
Authorization Decision
    ↓
Java Backend API

This pattern centralizes authorization and keeps backend services simple.

Key Takeaways

Technical Learnings

  • Built and deployed a Java Azure Function
  • Invoked Functions from APIM
  • Forwarded Bearer tokens through APIM
  • Decoded JWT tokens
  • Extracted appid claims
  • Integrated with Microsoft Graph
  • Explored Managed Identity authentication
  • Learned Graph permission requirements
  • Understood APIM policy debugging
  • Learned the difference between Liquid templates and APIM expressions

Architectural Learnings

The biggest realization was:

APIM should own API governance and token validation, while Azure Functions should handle custom authorization and business-specific logic.

This creates a scalable and reusable enterprise architecture.

What's Next?

APIM
    ↓
validate-jwt
    ↓
Azure Function
    ↓
Microsoft Graph
    ↓
Retrieve Entra Groups
    ↓
Inject Headers
       X-App-Name
       X-User-Groups
    ↓
Backend API

At that point, APIM becomes a true enterprise gateway and Azure Functions become a reusable authorization service for the entire platform.

A productive day of learning, troubleshooting, architecture design, Azure integration, and real-world API security patterns.