REST Lens
← Back to All Rules
Rule 21

XML Alternative

Category: Response Design

Description

Successful (2xx) responses should offer both JSON and XML content type representations to support diverse client requirements and maximize API compatibility.

What It Checks

This rule validates that all successful (2xx) responses provide both application/json and application/xml content types. It checks:

  • All operations (GET, POST, PUT, PATCH, DELETE) across your API
  • Only successful response codes (2xx status codes)
  • The presence of both application/json AND application/xml in the response content types
  • If one content type is present in a 2xx response, the other must also be present

The rule recursively examines all path nodes and their methods to ensure consistent dual-format support across the entire API.

Why It Matters

Offering both JSON and XML representations enhances API accessibility and compatibility:

  • Broader Client Support: Legacy systems and enterprise integrations often require XML, while modern applications prefer JSON
  • Standards Compliance: Many industries (healthcare, finance, government) have XML-based standards and regulations
  • Flexibility: Allows clients to choose the format that best suits their infrastructure and tooling
  • Future-Proofing: Prevents the need for breaking changes or versioning when clients request different formats
  • Interoperability: Facilitates integration with diverse technology stacks and platforms

Examples

Good ✅

paths:
  /users/{userId}:
    get:
      summary: Get user by ID
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: User found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
            application/xml:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: User not found

  /products:
    get:
      summary: List products
      responses:
        '200':
          description: Product list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'
            application/xml:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'
                xml:
                  name: products
                  wrapped: true

  /orders:
    post:
      summary: Create an order
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Order'
      responses:
        '201':
          description: Order created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
            application/xml:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          description: Invalid request

Bad ❌

paths:
  /users/{userId}:
    get:
      summary: Get user by ID
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: User found
          content:
            application/json:  # Only JSON, no XML
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: User not found

  /products:
    get:
      summary: List products
      responses:
        '200':
          description: Product list
          content:
            application/xml:  # Only XML, no JSON
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'

  /orders:
    post:
      summary: Create an order
      responses:
        '201':
          description: Order created
          content:
            application/json:  # Missing XML representation
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          description: Invalid request

Configuration

This rule has no configuration options. It is either enabled or disabled.

How to Fix

  1. Add both content types to all 2xx responses:

    # Before
    responses:
      '200':
        description: Success
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
    
    # After
    responses:
      '200':
        description: Success
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
          application/xml:
            schema:
              $ref: '#/components/schemas/User'
    
  2. Use the same schema for both formats:

    # Both formats can reference the same schema
    components:
      schemas:
        User:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
            email:
              type: string
    
  3. Add XML-specific metadata when needed:

    # For arrays, add XML wrapping configuration
    responses:
      '200':
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Product'
          application/xml:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Product'
              xml:
                name: products
                wrapped: true  # Wraps items in <products> element
    
  4. Implement content negotiation in your server:

    // Example implementation
    app.get('/users/:id', (req, res) => {
      const user = getUserById(req.params.id);
      const acceptHeader = req.headers.accept;
    
      if (acceptHeader.includes('application/xml')) {
        // Convert to XML and send
        res.type('application/xml');
        res.send(convertToXML(user));
      } else {
        // Default to JSON
        res.json(user);
      }
    });
    
  5. Apply to all successful responses consistently:

    # Ensure every 2xx response has both formats
    paths:
      /resource:
        get:
          responses:
            '200':  # Success - needs both formats
              content:
                application/json: {...}
                application/xml: {...}
            '201':  # Created - needs both formats
              content:
                application/json: {...}
                application/xml: {...}
            '204':  # No Content - no body, rule doesn't apply
              description: Deleted successfully
            '400':  # Error - not a 2xx, rule doesn't apply
              description: Bad request
    

Related Rules

  • Rule 8: Response Error Codes - Proper HTTP status code usage for different outcomes
  • Rule 53: Create/Update Return - Ensuring modify operations return resources in multiple formats
  • Rule 42: Consistent Errors - Maintaining consistency across response formats

Best Practices

  1. Content Negotiation: Implement proper Accept header handling to allow clients to request their preferred format
  2. Schema Reuse: Use the same schema definition for both JSON and XML to ensure consistency
  3. XML Metadata: Add appropriate XML annotations (like wrapped for arrays) when necessary
  4. Default Format: Choose a sensible default (typically JSON) when clients don't specify a preference
  5. Testing: Ensure both representations produce equivalent data structures
  6. Documentation: Clearly document that both formats are available in your API documentation

Implementation Notes

XML Array Wrapping

When returning arrays, XML requires proper wrapping:

# For a collection endpoint
schema:
  type: array
  items:
    $ref: '#/components/schemas/User'
  xml:
    name: users
    wrapped: true

This produces:

<users>
  <user>
    <id>1</id>
    <name>John</name>
  </user>
  <user>
    <id>2</id>
    <name>Jane</name>
  </user>
</users>

Without wrapped: true, you'd get:

<user>
  <id>1</id>
  <name>John</name>
</user>
<user>
  <id>2</id>
  <name>Jane</name>
</user>

Content Negotiation Example

Clients request their preferred format via the Accept header:

GET /users/123 HTTP/1.1
Accept: application/json

GET /users/123 HTTP/1.1
Accept: application/xml

GET /users/123 HTTP/1.1
Accept: application/json, application/xml;q=0.9

Common Violations

  1. JSON-only APIs: Modern APIs often forget XML support, limiting enterprise adoption
  2. Inconsistent format support: Some endpoints support both formats while others don't
  3. Missing XML configuration: Forgetting XML-specific metadata like array wrapping
  4. Request/response mismatch: Supporting both formats for requests but only one for responses