Skip to content

07 - Extending with Triggers (Flex Consumption)

Extend beyond HTTP using queue, blob, and timer triggers with annotation-based bindings and clear operational checks.

Prerequisites

Tool Version Purpose
JDK 17+ Compile and run Java functions locally
Maven 3.6+ Build and package Java artifacts
Azure Functions Core Tools v4 Start local host and publish artifacts
Azure CLI 2.61+ Provision Azure resources and inspect app state

Flex Consumption plan basics

Flex Consumption (FC1) keeps serverless economics while adding VNet integration, configurable instance memory (512 MB to 4096 MB), and per-function scaling. Microsoft recommends it for many new apps.

What You'll Build

You will add queue, blob, and timer triggers to a Java Function App using annotations, create the required storage resources, and validate end-to-end trigger firing.

Infrastructure Context

Plan: Flex Consumption (FC1) | Network: VNet integration supported

This tutorial adds non-HTTP triggers that require storage queues and blob containers.

flowchart TD
    TIMER["Timer\n(CRON schedule)"] --> FA[Function App\nFlex Consumption FC1]
    QUEUE["Storage Queue\nincoming-orders"] --> FA
    BLOB["Blob Container\nuploads/{name}"] --> FA
    FA --> LOGS["Application Insights\ntrace logs"]

    style FA fill:#0078d4,color:#fff
    style TIMER fill:#E8F5E9
    style QUEUE fill:#FFF3E0
    style BLOB fill:#E3F2FD
flowchart TD
    A[Queue message] --> B["@QueueTrigger handler"]
    C[Blob upload] --> D["@BlobTrigger handler"]
    E[Schedule] --> F["@TimerTrigger handler"]
    B --> G[Outputs and logs]
    D --> G
    F --> G

Steps

Step 1 - Create storage resources for triggers

# Create queue for queue trigger
az storage queue create \
  --name "incoming-orders" \
  --account-name "$STORAGE_NAME"

# Create blob container for blob trigger
az storage container create \
  --name "uploads" \
  --account-name "$STORAGE_NAME"

Step 2 - Review the queue trigger function

The reference app includes QueueProcessorFunction.java:

@FunctionName("queueProcessor")
public void run(
        @QueueTrigger(
            name = "message",
            queueName = "incoming-orders",
            connection = "QueueStorage")
        String message,
        final ExecutionContext context) {

    context.getLogger().info("Queue message received: " + message);
}

QueueStorage must use a real connection string

The connection = "QueueStorage" annotation references the QueueStorage app setting. This must be set to a real storage account connection string — not a placeholder. A fake AccountKey causes 403 errors when the queue listener starts, crashing the entire host.

Step 3 - Review the blob trigger function

The reference app includes BlobProcessorFunction.java:

@FunctionName("blobProcessor")
@StorageAccount("AzureWebJobsStorage")
public void run(
        @BlobTrigger(
            name = "content",
            path = "uploads/{name}",
            connection = "AzureWebJobsStorage",
            source = "EventGrid")
        byte[] content,
        @BindingName("name") String name,
        final ExecutionContext context) {

    context.getLogger().info("Processing blob: " + name + ", size: " + content.length + " bytes");
}

Step 4 - Review the timer trigger function

The reference app includes ScheduledCleanupFunction.java:

@FunctionName("scheduledCleanup")
public void run(
        @TimerTrigger(
            name = "timer",
            schedule = "0 0 2 * * *")
        String timerInfo,
        final ExecutionContext context) {

    context.getLogger().info("Scheduled cleanup executed at: " + java.time.Instant.now());
    context.getLogger().info("Timer info: " + timerInfo);
}

Step 5 - Build and publish

cd apps/java
mvn clean package
cd target/azure-functions/azure-functions-java-guide
func azure functionapp publish "$APP_NAME"

Step 6 - Validate trigger resources

# List queues
az storage queue list \
  --account-name "$STORAGE_NAME" \
  --output table

# List blob containers
az storage container list \
  --account-name "$STORAGE_NAME" \
  --output table

Step 7 - Test queue trigger

# Send a test message to the queue
az storage message put \
  --queue-name "incoming-orders" \
  --account-name "$STORAGE_NAME" \
  --content "test-flex-order-001"

# Check Application Insights for the processed message (wait 2-5 minutes)
az monitor app-insights query \
  --app "$APP_NAME" \
  --resource-group "$RG" \
  --analytics-query "traces | where message contains 'Queue message received' | order by timestamp desc | take 5"

Step 8 - Test blob trigger

# Upload a test file
echo "hello flex blob trigger" > /tmp/test-flex-upload.txt
az storage blob upload \
  --container-name "uploads" \
  --name "test-flex-upload.txt" \
  --file "/tmp/test-flex-upload.txt" \
  --account-name "$STORAGE_NAME" \
  --overwrite

# Check Application Insights for the processed blob (wait 2-5 minutes)
az monitor app-insights query \
  --app "$APP_NAME" \
  --resource-group "$RG" \
  --analytics-query "traces | where message contains 'Processing blob' | order by timestamp desc | take 5"

Step 9 - Verify all functions are registered

az functionapp function list \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --output table

Verification

Storage queue list:

Name
----------------
incoming-orders

Storage container list (showing trigger-related containers):

Name
---------------------
app-package
azure-webjobs-hosts
azure-webjobs-secrets
uploads

Function list showing all trigger types:

[
  {
    "name": "queueProcessor",
    "language": "java"
  },
  {
    "name": "blobProcessor",
    "language": "java"
  },
  {
    "name": "scheduledCleanup",
    "language": "java"
  },
  {
    "name": "timerLab",
    "language": "java"
  },
  {
    "name": "helloHttp",
    "language": "java"
  },
  {
    "name": "health",
    "language": "java"
  }
]

All 16 functions deployed and verified:

Function Type Status
health HTTP GET ✅ 200
helloHttp HTTP GET ✅ 200
info HTTP GET ✅ 200
logLevels HTTP GET ✅ 200
slowResponse HTTP GET ✅ 200
testError HTTP GET ✅ 500 (expected)
unhandledError HTTP GET ✅ 500 (expected)
dnsResolve HTTP GET ✅ 200
identityProbe HTTP GET ✅ 200
storageProbe HTTP GET ✅ 200
externalDependency HTTP GET ✅ 200
queueProcessor Queue ✅ Registered
blobProcessor Blob ✅ Registered
scheduledCleanup Timer ✅ Registered
timerLab Timer ✅ Registered
eventhubLagProcessor EventHub ✅ Registered

Flex Consumption per-function scaling

On Flex Consumption, queue and blob triggers benefit from per-function scaling — each function can scale independently based on its workload.

Clean Up

az group delete --name "$RG" --yes --no-wait

Next Steps

Done! You have completed all Flex Consumption plan tutorials for Java. Try another hosting plan:

See Also

Sources