Azure Function Apps | OkObjectResult Returns Empty JSON After Moving to .NET 9 Isolated Worker Runtime

Posted by Andrew Wilson on Wednesday, December 3, 2025
More in the Series

This post is part of the Working With Azure Function Apps series (4 posts)

The Problem

I recently upgraded an Azure Function from .NET 8 to .NET 9, and at the same time migrated from the in-process worker to the isolated worker model. After the upgrade, my function that returned OkObjectResult started returning an empty JSON object {} instead of the expected data.

[Function("MyFunction")]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Function,  "post")] HttpRequest req)
{
    var data = new MyResponse { Name = "Test", Value = 123 };
    return new OkObjectResult(data); // Returns {} instead of expected JSON
}

Why This Happens

There are two key changes that caused this issue:

1. In-Process to Isolated Worker Migration

The isolated worker process runs in a separate process from the Functions host. Unlike the in-process model, it doesn’t inherit any configuration or serialization settings. You’re starting with a clean slate and need to explicitly configure everything.

2. JSON Serializer Change

Starting with ASP.NET Core 3.0, Microsoft replaced Newtonsoft.Json with System.Text.Json as the default serializer. While both serialize JSON, they have different behaviors:

  • Newtonsoft.Json: More lenient, serializes public properties and fields
  • System.Text.Json: Stricter, only serializes public properties by default

When using OkObjectResult (an ASP.NET Core MVC type) in an isolated worker, you need to explicitly configure MVC services. Without this configuration, the serialization doesn’t work as expected.

The Solutions

You have two approaches to fix this, each with different trade-offs:

Embrace the isolated worker model by using the native types designed for it:

[Function("MyFunction")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
    var data = new MyResponse { Name = "Test", Value = 123 };
    
    var response = req.CreateResponse(HttpStatusCode.OK);
    await response.WriteAsJsonAsync(data); // Uses System.Text.Json by default
    
    return response;
}

Advantages:

  • Better Performance: System.Text.Json is significantly faster than Newtonsoft.Json
  • Lower Costs: Reduced latency means less execution time, which directly reduces Azure Functions costs (billed per execution time)
  • Lighter Dependencies: No need for additional MVC services or packages
  • Future-Proof: Aligned with the modern .NET isolated worker architecture
  • Native Support: Uses types specifically designed for isolated workers

Disadvantages:

  • Requires code changes to migrate from IActionResult to HttpResponseData
  • May need adjustments if you rely on specific Newtonsoft.Json features

Option 2: Add MVC Services with Newtonsoft.Json (Quick Fix)

If you need a quick fix or have complex serialization requirements, you can add MVC services with Newtonsoft.Json support to your Program.cs:

using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

// Add MVC services and configure Newtonsoft.Json
builder.Services.AddMvc().AddNewtonsoftJson();

builder.Build().Run();

You’ll also need to add the NuGet package:

dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

Advantages:

  • Minimal code changes - keeps existing OkObjectResult code working
  • Maintains backward compatibility with Newtonsoft.Json serialization behavior
  • Good for quick migration when you have tight deadlines

Disadvantages:

  • Higher Costs: Slower serialization means longer execution time and higher Azure Functions bills
  • Additional Dependencies: Requires MVC framework which adds overhead
  • Technical Debt: You’re opting back into older patterns instead of embracing the new architecture

Which Should You Choose?

For new projects or if you can afford the refactoring time: Go with Option 1 (native types). The performance benefits and cost savings will compound over time, especially for high-traffic functions.

For quick migrations with tight deadlines: Option 2 can get you unstuck quickly, but consider it temporary. Plan to refactor to native types when time permits.

Takeaway

When migrating to .NET 9 isolated worker Functions, you’re working in a fresh environment that requires explicit configuration. While adding Newtonsoft.Json gets things working quickly, embracing the native isolated worker types with System.Text.Json offers better performance and lower costs (⚠️important factors when Azure Functions are billed per execution time). Choose the approach that balances your immediate needs with long-term architecture goals.

Hope this helps, Happy Coding.

More in the Series

This post is part of the Working With Azure Function Apps series (4 posts)