Skip to content

HTTP API Patterns

This recipe shows production-ready Java HTTP trigger patterns: route parameters, query parsing, JSON body parsing, and response shaping across multiple HTTP methods.

Architecture

flowchart TD
    CLIENT[Client] --> API[HTTP Trigger Function]
    API --> VALIDATE[Validate route, query, body]
    VALIDATE --> RESPONSE[JSON Response + HTTP status]
    API -.-> APPINSIGHTS[Application Insights Logs]

Prerequisites

Use Java 17 and the Azure Functions Maven plugin:

<properties>
    <java.version>17</java.version>
    <azure.functions.maven.plugin.version>1.36.0</azure.functions.maven.plugin.version>
    <azure.functions.java.library.version>3.1.0</azure.functions.java.library.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.microsoft.azure.functions</groupId>
        <artifactId>azure-functions-java-library</artifactId>
        <version>${azure.functions.java.library.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.2</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-functions-maven-plugin</artifactId>
            <version>${azure.functions.maven.plugin.version}</version>
        </plugin>
    </plugins>
</build>

Create and run the app locally:

mvn --batch-mode clean package
mvn --batch-mode azure-functions:run

Java implementation

package com.contoso.functions;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;

import java.util.*;

public class HttpApiFunctions {
    private static final ObjectMapper MAPPER = new ObjectMapper();

    @FunctionName("ordersApi")
    public HttpResponseMessage run(
        @HttpTrigger(
            name = "request",
            methods = {HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT},
            authLevel = AuthorizationLevel.FUNCTION,
            route = "orders/{orderId}"
        )
        HttpRequestMessage<Optional<String>> request,
        @BindingName("orderId") String orderId,
        ExecutionContext context
    ) {
        try {
            if (request.getHttpMethod() == HttpMethod.GET) {
                String include = request.getQueryParameters().getOrDefault("include", "summary");
                Map<String, Object> payload = Map.of(
                    "orderId", orderId,
                    "include", include,
                    "status", "accepted"
                );
                return json(request, HttpStatus.OK, payload);
            }

            if (request.getHttpMethod() == HttpMethod.POST || request.getHttpMethod() == HttpMethod.PUT) {
                if (request.getBody().isEmpty()) {
                    return json(request, HttpStatus.BAD_REQUEST, Map.of("error", "Request body is required"));
                }

                JsonNode body = MAPPER.readTree(request.getBody().get());
                String customerId = body.path("customerId").asText("");
                if (customerId.isBlank()) {
                    return json(request, HttpStatus.BAD_REQUEST, Map.of("error", "customerId is required"));
                }

                Map<String, Object> payload = new LinkedHashMap<>();
                payload.put("orderId", orderId);
                payload.put("customerId", customerId);
                payload.put("items", body.path("items"));
                payload.put("method", request.getHttpMethod().name());
                payload.put("message", "Order payload processed");

                HttpStatus status = request.getHttpMethod() == HttpMethod.POST ? HttpStatus.CREATED : HttpStatus.OK;
                return json(request, status, payload);
            }

            return json(request, HttpStatus.METHOD_NOT_ALLOWED, Map.of("error", "Method not allowed"));
        } catch (Exception ex) {
            context.getLogger().warning("Failed to process request: " + ex.getMessage());
            return json(request, HttpStatus.BAD_REQUEST, Map.of("error", "Invalid JSON payload"));
        }
    }

    private HttpResponseMessage json(HttpRequestMessage<?> request, HttpStatus status, Object body) {
        return request.createResponseBuilder(status)
            .header("Content-Type", "application/json")
            .body(body)
            .build();
    }
}

Test each method:

curl --request GET "http://localhost:7071/api/orders/1001?include=details"

curl --request POST "http://localhost:7071/api/orders/1001" \
  --header "Content-Type: application/json" \
  --data "{\"customerId\":\"cust-01\",\"items\":[{\"sku\":\"A-100\",\"qty\":2}]}"

Implementation notes

  • Route parameters use @BindingName and stay strongly typed.
  • Query parameters come from request.getQueryParameters() and should have defaults.
  • For body parsing, validate required fields before processing business logic.
  • Build JSON responses with explicit Content-Type and meaningful status codes.

Review Matrix

Review area Page-specific check
Scope Confirm the guidance applies to HTTP API Patterns.
Source basis Validate the recommendation against the Microsoft Learn sources in this page.
Evidence Capture command output, portal state, metrics, logs, or screenshots before treating the result as proven.

See Also

Sources