# Nexus Client Code Generator

> For the complete documentation index, see [llms.txt](https://docs.temporal.io/llms.txt).
> Any documentation page is available as raw Markdown by appending `.md` to its URL.

> The Nexus Client Code Generator turns one schema into typed models, runtime validators, and Nexus Service definitions for Go, Java, Python, and TypeScript.

A [Nexus Service](/nexus/services) is a contract meant to be shared across team boundaries.
Those teams often work in different languages, so the same request and response types get hand-written for each SDK.
Hand-written copies drift: a field is required on one side and optional on the other, a bound is enforced by the caller but not the handler.

The **[Nexus Client Code Generator](https://github.com/temporalio/nexgen)** removes those copies.
You describe your types and [Nexus Operations](/nexus/operations) once in a definition file, and the generator emits the equivalent library code for Go, Java, Python, and TypeScript.
The generator is a command-line tool named `nexgen`, distributed from the [temporalio/nexgen](https://github.com/temporalio/nexgen) repository.

> **⚠️ Caution:**
>
> `nexgen` is pre-release software and may not retain backwards compatibility with previous versions of the tool.
> It is not yet published to any package registry, so you build it from source as described in [Install the generator](#install-the-generator).
>

## What the generator produces

The generator produces a client library in Go, Java, Python, or TypeScript for the inputs and outputs of your Nexus Operations. The generated types check values against the contract as they are sent and received, so a violation surfaces as an error rather than as bad data.

That client library contains three things:

- **A typed model.** An idiomatic struct, class, interface, or dataclass, with doc comments carried over from the schema.
- **A runtime validator.** One validator per type, applied when a value is parsed off the wire and again when it is serialized onto it. See [Validation guarantees](#validation-guarantees) for more.
- **A [Nexus Service Contract](/glossary#nexus-service-contract) definition**, for a file that declares Services. These are the Service and Operation declarations you register on a Worker and call from a caller Workflow. A pure JSON Schema file declares none, so it produces only the models and their validators.

Additionally, constraint failures are aggregated into a single native error listing every violation, each naming the offending field and the bound it broke.
A handler maps that error to a `BAD_REQUEST` [Nexus error](/nexus/error-handling), so a malformed request tells the caller everything that was wrong with it in one response.

The supported schema subset is deliberately strict.
Anything ambiguous, or anything that cannot be expressed identically in all generated languages, is rejected when you run the generator with a diagnostic explaining how to express it correctly.
The generator prefers to fail loudly at generation time over emitting code that behaves differently in one language than another.

## Supported languages

`nexgen` generates Go, Java, Python, and TypeScript.

## Definition files

Types are modeled with [JSON Schema 2020-12](https://json-schema.org/draft/2020-12).
A definition file is one of two kinds, decided by what sits at its root.
A file is one or the other, never both.

**Pure JSON Schema.** The root of the document is itself a type, and reusable types live under `$defs`.
Use this when you only need data models shared across languages, with no Service or Operation declarations.

**Nexus document.** Add a root `nexusrpc: "1.0.0"` marker to enable a `services` section.
The root becomes an envelope: Services and their Operations sit at the top level, and your types live under `$defs`.
Only this kind can declare a Service.

The two kinds compose across files, so a contract is not limited to one of them.
A Service contract is often a Nexus document declaring the Services and Operations, plus pure JSON Schema files holding the types it `$ref`s by relative path.
The [`kb/`](https://github.com/temporalio/nexgen/tree/main/samples/schemas/kb) closure described below is built that way.

The examples on this page use [`samples/schemas/chat.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/chat.nexusrpc.yaml) from the repository, abbreviated here:

```yaml
nexusrpc: '1.0.0'
$schema: https://json-schema.org/draft/2020-12/schema
services:
  ChatService:
    fqn: example.chat.v1.ChatService
    description: Send messages and look up rooms.
    operations:
      sendMessage:
        description: Post a message to a room.
        input: { $ref: '#/$defs/SendMessageInput' }
        output: { $ref: '#/$defs/SendMessageOutput' }
      getRoom:
        description: Look up a room by id.
        input:
          type: object
          additionalProperties: false
          properties:
            roomId: { type: string }
          required: [roomId]
        output: { $ref: '#/$defs/Room' }
      ping:
        description: Liveness probe.
$defs:
  SendMessageInput:
    type: object
    additionalProperties: false
    properties:
      roomId: { type: string }
      message: { $ref: '#/$defs/Message' }
    required: [roomId, message]
  SendMessageOutput:
    type: object
    additionalProperties: false
    properties:
      messageId: { type: string }
    required: [messageId]
```

See [Definition files](https://github.com/temporalio/nexgen#definition-files) in the generator's README for details on that file.

### How names are derived

You write two kinds of name in the schema: one for the Service, and one for each Operation. In the sample above they are `ChatService` and `sendMessage`:

```yaml
services:
  ChatService: # the Service name
    operations:
      sendMessage: # the Operation name
```

Those are the only names you write. From each one the generator produces two more: a wire name and a name in your code. You declare neither of them.

Give each name the casing that matches what it becomes:

- A **Service** name is PascalCase: `ChatService`. A Service becomes a type in the generated code, and types are PascalCase.
- An **Operation** name is camelCase: `sendMessage`. An Operation becomes a method on that type, and the generator cases it like any other member.

The first letter is the part the generator enforces: a Service has to start uppercase and an Operation lowercase. Both must begin with a letter and contain only letters and digits.

```
service name `chatService` must match `^[A-Z][a-zA-Z\d]+$` (start uppercase, then
letters/digits); set the wire name via `fqn` if it must differ
```

> **📝 Note:**
> Overriding the wire name
>
> The `fqn` in that error — a fully qualified name — is optional, and you can skip it to start.
>
> It lets a Service or Operation carry a wire name of your choosing rather than the one the generator derives from the name you wrote. Because it never becomes a code identifier, it accepts characters a name cannot: that is how a Service gets a wire name of `example.chat.v1.ChatService`, or an Operation one of `poll-messages`. Wire names are covered just below.
>
> Use `fqn` when you need to match a contract that is already published, or when you want a versioned, namespaced wire name. Otherwise leave it out.
>

**The wire name** is the string the caller and the handler exchange, and what appears in Event History and the Temporal UI. Neither side types it — both take it from the generated code.

Unless you override it, the wire name is the name from the definition file converted to PascalCase. In the sample that gives the Service a wire name of **`ChatService`** and the `sendMessage` Operation a wire name of **`SendMessage`**.

In the sample above the Service does override it using `fqn`, so the Service wire name becomes **`example.chat.v1.ChatService`**.

**The name in your code** is what you call. The generator recases the name you wrote to match the conventions of the language it is generating for. With no override in play, the two derived names line up like this:

| You write | Wire name | Java | Go | Python | TypeScript |
| --- | --- | --- | --- | --- | --- |
| `ChatService` | `ChatService` | `ChatService` | `ChatService` | `ChatService` | `chatService` |
| `sendMessage` | `SendMessage` | `sendMessage` | `SendMessage` | `send_message` | `sendMessage` |

Operations become camelCase methods in Java and TypeScript, snake_case in Python, and PascalCase in Go.

An Operation's `input` and `output` are each optional.
The `ping` Operation above declares neither, which generates an Operation that takes and returns nothing.
When present, each must be an object type, so that a field can be added later without breaking the wire format.

The repository holds four example definitions under [`samples/schemas/`](https://github.com/temporalio/nexgen/tree/main/samples/schemas):
[`chat.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/chat.nexusrpc.yaml),
the feature-diverse [`showcase.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/showcase.nexusrpc.yaml),
the pure-schema [`temporal.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/temporal.yaml),
and a multi-file closure under [`kb/`](https://github.com/temporalio/nexgen/tree/main/samples/schemas/kb) showing how types split across files resolve through `$ref`.
The `kb/` closure starts at [`kb.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/kb/kb.nexusrpc.yaml) and pulls in types from its [`content/`](https://github.com/temporalio/nexgen/tree/main/samples/schemas/kb/content) and [`tree/`](https://github.com/temporalio/nexgen/tree/main/samples/schemas/kb/tree) subdirectories.

## Install the generator

Build the `nexgen` binary from source with Cargo, the Rust build tool:

```bash
git clone https://github.com/temporalio/nexgen.git
cd nexgen
cargo build --release
```

The binary lands at `target/release/nexgen`.
Confirm it works and check which targets your build supports:

```bash
./target/release/nexgen --version
./target/release/nexgen --help
```

## Generate code

Every language uses the same shape: `nexgen <language> <input>... --output <dir>`.
Inputs are positional and may be files or directories, so you can pass a whole multi-file schema closure. Some languages have extra flags.

> **📝 Note:**
>
> The output directory name becomes the generated package or module name.
> Name it after your domain, such as `chat`, not after the language.
> Pointing `--output` at a directory named `go` produces `package go`, which is not valid Go.
>

### Go

```bash
nexgen go samples/schemas/chat.nexusrpc.yaml --output ./chat
```

Place the output directory inside your Go module.
The package name is the directory name, so the example above generates `package chat` in `./chat/chat.go` alongside `./chat/definitions.go`.

### Java

Java requires `--package-name`. Point `--output` at the **full package path** beneath your source root, not just a directory named after the last segment:

```bash
nexgen java samples/schemas/chat.nexusrpc.yaml \
  --output ./src/main/java/com/example/chat \
  --package-name com.example.chat
```

The generator checks only that the package name's last dot-separated segment matches the output directory's name. If they disagree, generation stops and tells you how to reconcile them:

```
`--package-name com.example.wrong` must end with the output directory name `chat`,
but its last segment is `wrong`; point `--output` at a directory named `wrong` or
change the package's last segment to `chat`
```

> **⚠️ Caution:**
> Passing that check is not enough to compile
>
> The check compares one segment; Java requires the file's location to match its whole package declaration. `--output ./src/chat --package-name com.example.chat` passes, because `chat` matches `chat`, and still produces files that declare `package com.example.chat` while sitting at `src/chat/`.
>
> Nothing fails at generation time, and nothing necessarily fails when you compile the generated files on their own. It breaks when something imports them:
>
> ```
> Main.java:1: error: package com.example.chat does not exist
> import com.example.chat.ChatService;
> ```
>
> Always give `--output` the entire package path beneath your source root — `./src/main/java/com/example/chat` for `com.example.chat`. If files land in the wrong place, delete them and generate again with a corrected `--output`, rather than editing the `package` line to match.
>

### Python

```bash
nexgen python samples/schemas/chat.nexusrpc.yaml --output ./chat
```

This writes an importable package: `models.py`, `services.py`, and an `__init__.py` that re-exports both. Place it where the code that imports it can reach it — the output directory *is* the package.

That also means the directory name becomes an importable module name, so pick one that does not collide with the standard library. `./chat` is safe, but a Service whose subject happens to share a name with a stdlib module is not: `./email`, `./queue`, and `./calendar` each shadow one for anything on that path. Qualify those — `./calendar_v1`.

> **📝 Note:**
> Python output needs Pydantic
>
> Generated models are [Pydantic](https://docs.pydantic.dev/) models. Installing the Python SDK on its own does not bring Pydantic with it, so importing the generated package fails with `ModuleNotFoundError: No module named 'pydantic'` until you add it. The SDK ships an extra for this:
>
> ```bash
> pip install 'temporalio[pydantic]'
> ```
>
> Your Worker and Client then need the Pydantic Data Converter — see [Use Pydantic models](/develop/python/data-handling/data-conversion#use-pydantic-models) for the setup.
>
> That converter is not optional if your schema uses any temporal `format`. `datetime.date`, `datetime.time`, and `datetime.datetime` can only be converted by it, and the generator maps `date`, `time`, and `date-time` onto those types.
>

### TypeScript

```bash
nexgen ts samples/schemas/chat.nexusrpc.yaml --output ./chat
```

TypeScript accepts `--date-time-types` to choose how date and time fields are represented in memory. There are three choices:

- `string`, the default, keeps every date and time field as the [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) string that appears on the wire. It adds no runtime dependency and round-trips losslessly, but you parse and compare the strings yourself.
- `date` maps `date-time` fields to a JavaScript `Date`. This is lossy: a `Date` is a UTC instant, so the original offset is folded away and precision is capped at milliseconds.
- `temporal` maps to the [TC39 Temporal API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal), a JavaScript standard for dates and times that is unrelated to Temporal the platform. It preserves the offset and sub-second precision, and requires the `Temporal` global.

The chat schema has no date or time fields, so this command uses [`temporal.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/temporal.yaml), which has one field for each of `date`, `date-time`, `time`, and `duration`:

```bash
nexgen ts samples/schemas/temporal.yaml --output ./events --date-time-types temporal
```

## Dates, times, and durations

TypeScript's `--date-time-types` is the only place you choose how a date or time is represented. Elsewhere the generator decides: Java uses `java.time`, Python `datetime` and `timedelta`, and Go `time.Time` and `time.Duration`. Two cases hand you the wire string to work with instead of a date type — `format: time` in Java, and every date and time format under TypeScript's default `string` mode.

Whichever type you get, every language writes the same bytes. Dates and times use [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339), which is a profile of [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). ISO 8601 permits many optional spellings of the same instant, and RFC 3339 narrows them to one so two systems cannot read a timestamp differently. RFC 3339 specifies timestamps rather than durations, so durations follow ISO 8601.

## How validation works

Validation is the one behavior the generated types add, so a call can fail with a contract violation that hand-written types would not have caught. Only the wiring of that validation differs between languages.

| SDK        | How validation reaches the wire                             | Extra step |
| ---------- | ----------------------------------------------------------- | ---------- |
| Go         | Generated `MarshalJSON` and `UnmarshalJSON` on each model    | None       |
| Java       | Generated Jackson serializer and deserializer on each model  | None       |
| Python     | Pydantic model validation                                    | [Use the Pydantic data converter](/develop/python/data-handling/data-conversion#use-pydantic-models) |
| TypeScript | Generated mapper classes                                     | Call the mapper yourself |

In Go, Java, and Python the validator sits in the serialization hook the Temporal data converter already calls, so validation happens on its own once the models are in use.
TypeScript requires an explicit call, covered in [Validate payloads in TypeScript](#validate-payloads-in-typescript).

### Validation guarantees

The two directions do not check the same things.

**Parsing a value off the wire** enforces required fields and every value constraint, aggregating all violations into one error. This is the direction that protects a handler from a malformed request.

**Serializing a value onto the wire** enforces value constraints — lengths, bounds, counts, patterns. It does not report a required field you left unset. The field is omitted from the payload and the peer rejects it, so the failure surfaces as a `BAD_REQUEST` from the other side rather than as a local error at the point you built the object.

For code that catches and logs a violation, see the per-language examples in [Use the generated code](#use-the-generated-code).

## Use the generated code

**Whether the code is generated or written by hand, you use it the same way.** It is a Service definition and a set of types: register it on a Worker, and call it from a caller Workflow exactly as described in your SDK's Nexus guide. The one difference is that generated types validate themselves, so a call can fail with a contract violation for you to catch.

Each example below registers a handler, calls the Operation from a caller Workflow, and catches a validation failure.

### Go

The generated `ChatService` value carries the Service name and one typed Operation reference per Operation.
Register handlers on a Worker:

```go
service := nexus.NewService(chat.ChatService.ServiceName)

sendMessage := nexus.NewSyncOperation(chat.ChatService.SendMessage.Name(),
    func(ctx context.Context, input chat.SendMessageInput, _ nexus.StartOperationOptions) (chat.SendMessageOutput, error) {
        return chat.SendMessageOutput{MessageId: store(input)}, nil
    })

if err := service.Register(sendMessage); err != nil {
    return err
}
w.RegisterNexusService(service)
```

Call it from a caller Workflow, passing the generated Operation reference so the SDK type-checks the request and response. A payload that violates the contract fails at the call, so that is where you catch it:

```go
client := workflow.NewNexusClient("chat-endpoint", chat.ChatService.ServiceName)

var output chat.SendMessageOutput
err := client.ExecuteOperation(
    ctx,
    chat.ChatService.SendMessage,
    chat.SendMessageInput{RoomId: "r1", Message: chat.Message{Kind: "text", Body: "hi"}},
    workflow.NexusOperationOptions{},
).Get(ctx, &output)

if err != nil {
    var validationErr *chat.ValidationError
    if errors.As(err, &validationErr) {
        for _, v := range validationErr.Violations {
            logger.Error("contract violation", "path", v.Path, "reason", v.Reason)
        }
    } else {
        logger.Error("Nexus call failed", "error", err)
    }
    return err
}
```

`ValidationError` carries every violation as a `Violation` with a `Path` and a `Reason`. Reach it with `errors.As` rather than a type assertion, because the error arrives wrapped by the JSON encoder. It is generated into each package, so a consumer of two generated Services needs one `errors.As` per package.

### Java

The generator emits `ChatService` as an interface annotated with `@Service`, with one `@Operation` method per Operation.
On the handler side, write a separate implementation class that points at the generated interface with `@ServiceImpl`, and return an `OperationHandler` from each `@OperationImpl` method:

```java
@ServiceImpl(service = ChatService.class)
public final class ChatServiceImpl {
    @OperationImpl
    public OperationHandler<SendMessageInput, SendMessageOutput> sendMessage() {
        return OperationHandler.sync((ctx, details, input) -> new SendMessageOutput(store(input)));
    }
}
```

Register it on a Worker with `worker.registerNexusServiceImplementation(new ChatServiceImpl())`.

On the caller side, the same interface works directly as a Workflow stub. A payload that violates the contract fails at the call, so that is where you catch it:

```java
ChatService chat = Workflow.newNexusServiceStub(
        ChatService.class,
        NexusServiceOptions.newBuilder()
                .setEndpoint("chat-endpoint")
                .setOperationOptions(NexusOperationOptions.newBuilder()
                        .setScheduleToCloseTimeout(Duration.ofSeconds(10))
                        .build())
                .build());

try {
    SendMessageOutput output = chat.sendMessage(new SendMessageInput("r1", message));
} catch (DataConverterException e) {
    if (e.getCause() instanceof ValidationException ve) {
        ve.getViolations().forEach(v -> log.error("{}: {}", v.getPath(), v.getReason()));
    } else {
        log.error("Payload conversion failed", e);
    }
    throw e;
}
```

`ValidationException` extends Jackson's `JsonMappingException`, so it is checked and always arrives wrapped. Converting the payload is what triggers it, so it reaches you as the cause of a `DataConverterException`, with the violation list intact.

It is generated into each package, so a consumer of two generated Services has two unrelated exception types of the same name and needs a `catch` per package.

### Python

The generator emits `ChatService` as a `@service`-decorated class whose attributes are typed `Operation` declarations.
Bind a handler to it:

```python
@service_handler(service=ChatService)
class ChatServiceHandler:
    @sync_operation
    async def send_message(
        self, ctx: StartOperationContext, input: SendMessageInput
    ) -> SendMessageOutput:
        return SendMessageOutput(messageId=store(input))
```

Pass the handler to your Worker as `nexus_service_handlers=[ChatServiceHandler()]`, then call it from a caller Workflow:

```python
client = workflow.create_nexus_client(service=ChatService, endpoint="chat-endpoint")

output = await client.execute_operation(
    ChatService.send_message,
    SendMessageInput(roomId="r1", message=Message(kind="text", body="hi")),
)
```

Generated Python fields are snake_case with the wire name as an alias.
Construct models with either name, and read them with the snake_case attribute: `SendMessageInput(roomId="r1", ...)` constructs, and `output.message_id` reads.

There is nothing to catch at the call. Pydantic validates when the model is constructed, so an invalid `SendMessageInput` raises `pydantic.ValidationError` at the constructor and never reaches the Nexus client. Handle it where you build the model.

### TypeScript

The generator emits a `chatService` Service definition plus, for each type, an interface and a companion `<Type>Mapper` class:

```typescript
export const chatService = nexus.service('example.chat.v1.ChatService', {
  sendMessage: nexus.operation<SendMessageInput, SendMessageOutput>({ name: 'SendMessage' }),
  getRoom: nexus.operation<GetRoomInput, Room>({ name: 'GetRoom' }),
  ping: nexus.operation<void, void>({ name: 'Ping' }),
});
```

Register a handler against that definition with `nexus.serviceHandler(chatService, { ... })`, and create a caller with `workflow.createNexusServiceClient({ service: chatService, endpoint: 'chat-endpoint' })`.

#### Validate payloads in TypeScript

> **⚠️ Caution:**
>
> In TypeScript the generated validator only runs when you call the mapper.
> No generated payload converter exists, so nothing calls it for you.
>

Each generated type comes with a mapper exposing two methods.
`fromIntermediate` validates an untrusted plain value and returns the typed model.
`toIntermediate` validates a model and returns its plain wire form.
Call them at both edges of every Operation, on the handler side and the caller side:

```typescript
const handler = nexus.serviceHandler(chatService, {
  async sendMessage(_ctx, input) {
    const request = new SendMessageInputMapper().fromIntermediate(input);
    const output = { messageId: await store(request) };
    return new SendMessageOutputMapper().toIntermediate(output) as SendMessageOutput;
  },
});
```

The caller side is the mirror image. Map the request out before executing the Operation, and map the result back in when it returns:

```typescript
const client = workflow.createNexusServiceClient({
  service: chatService,
  endpoint: 'chat-endpoint',
});

const wire = new SendMessageInputMapper().toIntermediate(input) as SendMessageInput;
const raw = await client.executeOperation(chatService.operations.sendMessage, wire);
const output = new SendMessageOutputMapper().fromIntermediate(raw);
```

The cast is expected in both examples: `toIntermediate` returns `unknown`, because its result is a plain wire value rather than the model type the Operation declares.

Skipping the mapper is the failure to watch for, because nothing reports it.
The value handed to your handler is typed as the model, since `nexus.operation<SendMessageInput, SendMessageOutput>` declares it that way, but at runtime it is only whatever was deserialized.
A handler that ignores the mapper compiles, type-checks, and returns correct results for valid payloads, while enforcing none of the constraints in your schema.

When a payload does violate the contract, `fromIntermediate` throws a `ValidationError` carrying every violation at once:

```
ValidationError: 2 validation error(s): roomId: required; message.body: expected string
```

The error also exposes a `violations` array of `{ path, reason }` objects, so a handler can convert it into a `BAD_REQUEST` Nexus error with the full list intact.

## Regenerate after a contract change

Generated files carry a `DO NOT EDIT` header and are replaced wholesale on the next run. There is no merge step, so anything you add to them is lost.

Two habits make this safe:

- **Commit generated code and regenerate as its own commit.** The diff then shows exactly what the contract change did to each language.
- **Fix names in the schema, not the output.** When a generated identifier is wrong for your language, set a per-language override in the contract so the fix survives regeneration. See [Naming and overrides](https://github.com/temporalio/nexgen#naming--overrides) for the available keys.

If you generate into the wrong directory, delete what landed there and run the generator again with corrected flags. Do not edit the `package` or module declaration to match where the files ended up as the next run will overwrite it.

## Schema defaults

A property can declare a `default`, which makes it optional for a caller to supply:

```yaml
sampleValue:
  type: integer
  default: 0
```

A caller that leaves `sampleValue` unset sends a payload without the field, and the receiver reads `0`. The default is never written into the payload, so an omitted field stays omitted rather than being filled in before it is sent.

> **⚠️ Caution:**
> Changing a default is a breaking change
>
> The default lives in the generated code, not in the payload. Temporal replays a Workflow by re-reading the payloads already recorded in its [Event History](/encyclopedia/event-history) using whatever code the Worker is running now, so changing a default changes what those recorded payloads mean.
>
> If the value affects which commands the Workflow produces, replaying an in-flight Workflow fails with a [non-determinism error](/troubleshooting/execution-failures#non-determinism-error) — the [deterministic constraints](/workflow-definition#deterministic-constraints) that govern any change to Workflow code apply here too. If it does not affect commands, nothing fails and the behavior changes silently, which is harder to catch.
>
> Treat a default as part of the contract. Adding a replacement field is not sufficient on its own: old payloads omit the new field too, so replay reads that field's default and can still diverge. Any change to how existing payloads are interpreted needs a [Workflow versioning](/workflow-definition#workflow-versioning) plan that keeps in-flight Executions on their original behavior.
>

Because the field is optional, Go and Java give you two members side by side, so nothing depends on remembering that a default exists:

- The field itself, which is empty when the caller omitted it — `getSampleValue()` returns `null` in Java, and `SampleValue` is a `nil *int64` in Go.
- An accessor named after it that substitutes the default — `getSampleValueOrDefault()` and `SampleValueOrDefault()`.

Use the first when you need to know whether the caller supplied a value, and the second when you just want a number.

TypeScript has no accessor. `sampleValue` is `undefined` when unset, and the generator exports a `DEFAULT_SAMPLE_VALUE` constant you apply yourself: `sampleValue ?? DEFAULT_SAMPLE_VALUE`.

Python has neither. Pydantic applies defaults when the model is constructed, so `sample_value` always holds a value and an omitted field reads the same as one explicitly set to `0`.

## Supported schema features

The generator implements a curated subset of [JSON Schema 2020-12](https://json-schema.org/draft/2020-12) chosen so that every accepted construct lowers identically into all generated languages.

Fully supported: `properties`, `required`, `default`, `minProperties` and `maxProperties`, `dependentRequired`, string and numeric bounds, `items`, `minItems` and `maxItems`, `minContains` and `maxContains`, `allOf`, the recognized nullable pattern `oneOf: [{type: T}, {type: "null"}]`, and the `title`, `description`, and `deprecated` annotations.

Partially supported: `type` (single-string form only), `additionalProperties`, `propertyNames`, `const` and `enum` (scalars only), `format`, `pattern` (a portable RE2-safe subset), `multipleOf`, `contentEncoding`, `uniqueItems`, `contains`, `oneOf` (branches must be separable by a decidable selector), and `$ref` with `$defs` (local files only).

Deliberately rejected, because they have no coherent typed lowering across all generated languages: `anyOf`, `not`, `if`/`then`/`else`, `dependentSchemas`, `prefixItems`, `unevaluatedProperties`, `unevaluatedItems`, `contentMediaType`, and `contentSchema`.

For the current per-keyword support table, see the [nexgen README](https://github.com/temporalio/nexgen#supported-json-schema-features).

> **💡 Tip:**
> RESOURCES
>
> - [temporalio/nexgen](https://github.com/temporalio/nexgen) for the generator, its README, and the example schemas.
> - [Nexus Services](/nexus/services) for the Service contract concept.
> - Nexus feature guides for registering Services and calling Operations:
>   [Go](/develop/go/nexus/feature-guide) |
>   [Java](/develop/java/nexus/feature-guide) |
>   [Python](/develop/python/nexus/feature-guide) |
>   [TypeScript](/develop/typescript/nexus/feature-guide)
>
