# OR
type: string
format: decimal
```

Both map to `java.math.BigInteger` and `java.math.BigDecimal` respectively, with convenient builder overloads:

```java
// Object builders accept primitives directly
Payment.builder()
    .amount(99.99)              // Accepts double, converts to BigDecimal
    .transactionId(12345L)      // Accepts long, converts to BigInteger  
    .build();
```

#### Union Types

Support for polymorphic types uses OpenAPI's `oneOf` keyword with different strategies based on discriminator presence.

**Non-discriminated oneOf** uses composition:

```yaml
Pet:
  oneOf:
    - $ref: "#/components/schemas/Cat"
    - $ref: "#/components/schemas/Dog"
```

```java
Cat cat = Cat.builder().name("Whiskers").build();
Dog dog = Dog.builder().name("Rex").build();

// Pet.of accepts only Cat or Dog types
Pet pet = Pet.of(cat);

// Type inspection for handling
if (pet.value() instanceof Cat cat) {
    System.out.println("Cat: " + cat.name());
} else if (pet.value() instanceof Dog dog) {
    System.out.println("Dog: " + dog.name());
}
```

**Discriminated oneOf** uses inheritance:

```yaml
Pet:
  oneOf:
    - $ref: "#/components/schemas/Cat"
    - $ref: "#/components/schemas/Dog"
  discriminator:
    propertyName: petType
```

```java
Pet cat = Cat.builder().name("Whiskers").build();  // Cat implements Pet
Pet dog = Dog.builder().name("Rex").build();       // Dog implements Pet
```

**oneOf Type Erasure Handling:**

When generic types would conflict, suffixed factory methods are generated:

```yaml
Info:
  oneOf:
    - type: array
      items: { type: integer }
      x-speakeasy-name-override: counts
    - type: array  
      items: { type: string }
      x-speakeasy-name-override: descriptions
```

```java
// Generates specialized factory methods to avoid erasure
Info countsInfo = Info.ofCounts(List.of(1, 2, 3));
Info descriptionsInfo = Info.ofDescriptions(List.of("a", "b", "c"));
```

**anyOf Support:**

The `anyOf` keyword is treated as `oneOf` with forgiving deserialization—when multiple subtypes match, the heuristic selects the subtype with the greatest number of matching properties.

#### Enums

**Closed Enums** (standard Java enum):

```java
public enum Color {
    RED("red"), GREEN("green"), BLUE("blue");
    
    @JsonValue
    private final String value;
    
    public String value() { return value; }
    
    public static Optional<Color> fromValue(String value) {
        // Returns Optional.empty() for unknown values
    }
}
```

**Open Enums** with `x-speakeasy-unknown-values: allow`:

```yaml
Color:
  type: string
  enum: [red, green, blue]
  x-speakeasy-unknown-values: allow
```

Generates a concrete class instead of Java enum:

```java
// Looks like enum but handles unknown values
Color red = Color.RED;                    // Static constants
Color unknown = Color.of("purple");       // Handles unknown values
boolean isUnknown = unknown.isUnknown();   // Check if value is unknown

// For switch expressions, convert to real enum
unknown.asEnum().ifPresent(knownColor -> {
    switch (knownColor) {
        case RED -> System.out.println("Red");
        // ... handle known values
    }
});
```

#### Custom Serialization

You **must** use the generated custom Jackson `ObjectMapper` for serialization/deserialization:

```java
// Access the singleton ObjectMapper
ObjectMapper mapper = JSON.getMapper();

// Serialize/deserialize generated objects
String json = mapper.writeValueAsString(user);
User user = mapper.readValue(json, User.class);
```

### Build Customization

- **Preserve customizations**: Use `build-extras.gradle` for additions (untouched by generation updates)
- **Add plugins**: Use `additionalPlugins` property in `gen.yaml`
- **Manage dependencies**: Add to `build-extras.gradle` or use `additionalDependencies` in `gen.yaml`

```yaml
java:
  version: 0.2.0
---
additionalPlugins:
  - 'id("java")'
additionalDependencies:
  - implementation:com.fasterxml.jackson.core:jackson-databind:2.16.0
```

### Logging & Observability

#### SLF4J Integration

The Java SDK includes built-in SLF4J logging integration that provides structured logging across all SDK operations without requiring specific logging implementations. This backend-agnostic approach allows library authors to include logging without depending on particular logging frameworks.

**Configuration:**

SLF4J logging is enabled by default for new SDKs via the `enableSlf4jLogging` configuration option. The feature can be configured in `gen.yaml`:

```yaml
java:
  version: 1.0.0
  enableSlf4jLogging: true  # Default: true for new SDKs
```

**Log Levels:**

- **DEBUG**: High-level operations (request/response cycles, retry attempts, hook executions)
- **TRACE**: Detailed information (pagination state, streaming lifecycle, backoff calculations)

**Logging Coverage:**

- **HTTP Client**: Request/response logging with sensitive header redaction
- **Retry Logic**: Attempt tracking, backoff calculations, and exhaustion reporting
- **Pagination**: Page fetch tracking and state management
- **Streaming**: Initialization, item processing, and lifecycle events
- **Hooks**: Execution counts, operation IDs, and exception handling

**Setup Example:**

Add a logging implementation dependency to your project:

```gradle
dependencies {
    implementation 'org.slf4j:slf4j-api:2.0.9'
    implementation 'ch.qos.logback:logback-classic:1.5.6'  // Example implementation
}
```

Create a `logback.xml` configuration file:

```xml
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Set SDK logging to DEBUG level -->
    <logger name="com.example.sdk" level="DEBUG"/>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>
```

**Backward Compatibility:**

Legacy `enableHTTPDebugLogging()` continues to work for existing SDKs. Both logging approaches can coexist during migration periods.

**Security:**

Sensitive headers (Authorization, X-API-Key, etc.) are automatically redacted in HTTP request/response logs to prevent credential leakage.

### Dependencies

The SDK maintains minimal dependencies:

- [Jackson Library](https://github.com/FasterXML/jackson) for JSON serialization/deserialization
- [Apache HttpClient](https://hc.apache.org/httpcomponents-client-4.5.x/index.html) for HTTP requests
- [Jayway JsonPath](https://github.com/json-path/JsonPath) for JSON path expressions in Speakeasy metadata
- [SLF4J API](https://www.slf4j.org/) for structured logging (when enabled)

## Configuration Reference

### Parameters & Method Generation

Method parameter handling is controlled by the `maxMethodParams` configuration in `gen.yaml`:

```yaml
java:
  version: 1.0.0
  maxMethodParams: 5  # Default threshold
```

**When parameter count ≤ `maxMethodParams`:**
```java
// Parameters become method arguments
User user = sdk.getUser("user-123", true, "email,name");
```

**When parameter count > `maxMethodParams` or `maxMethodParams = 0`:**
```java  
// All parameters wrapped in request object
GetUserRequest request = GetUserRequest.builder()
    .userId("user-123")
    .includeMetadata(true) 
    .fields("email,name")
    .build();
    
User user = sdk.getUser(request);
```

### Default Values & Constants

The SDK handles OpenAPI `default` and `const` keywords with lazy-loading behavior:

#### Default Values

Fields with `default` values use `Optional` wrappers and lazy-loading:

```yaml