Override TypeSpec types with Java-native types (e.g. OffsetDateTime, DayOfWeek) using @@alternateType in a client.java.tsp file. Use when a TypeSpec model field has an incorrect or too-generic type that should map to a specific Java type.
npx skills add https://github.com/Azure/azure-sdk-for-java --skill tsp-type-override
Override types in generated Java code by adding @@alternateType decorators to the client.java.tsp file.
tsp-location.yaml.TempTypeSpecFiles/. If not, run tsp-client sync first.client.java.tsp file inside TempTypeSpecFiles/. This is the only file you should edit.TempTypeSpecFiles/ is a transient working directory managed by tsp-client. It is regenerated on every tsp-client sync or tsp-client update and is typically gitignored. Any changes made only in TempTypeSpecFiles/ will be lost on the next sync/update cycle.
If the user provides a local checkout of Azure/azure-rest-api-specs, always apply the same client.java.tsp edits there so the changes are preserved and can be committed to a PR. The path to the spec file inside that repo can be derived from tsp-location.yaml:
directory field gives the relative path (e.g. specification/ai-foundry/data-plane/Foundry)client.java.tsp inside that directorySearch the .tsp files under TempTypeSpecFiles/ for the model and field the user wants to override:
grep -rn "<ModelName>\|<fieldName>" TempTypeSpecFiles/ --include="*.tsp"
Read the model definition to confirm the current type of the target field (e.g. string, int32, a union, etc.).
There are two forms of @@alternateType. Choose based on the target type:
Use when there is a TypeSpec scalar that the Java emitter already maps to the desired Java type.
| Desired Java type | TypeSpec alternate |
|-----------------------|------------------------|
| OffsetDateTime | utcDateTime |
| Duration | duration |
| byte[] | bytes |
| long / Long | int64 |
| double / Double | float64 |
Syntax (applied to a model property, scoped to Java):
@@alternateType(ModelName.fieldName, utcDateTime, "java");
This form is fully supported on model properties.
Use when no TypeSpec scalar maps to the desired Java type (e.g. java.time.DayOfWeek), or when you want to prevent emission of a model entirely by mapping it to an existing external class (e.g. an openai-java type).
Syntax (applied to the type definition itself, not a property):
@@alternateType(TypeName, { identity: "fully.qualified.ClassName" }, "java");
> Important constraints for external types:
> - External types ({ identity: ... }) cannot be applied to model properties — they must target the type definition (Model, Enum, Union, Scalar).
> - A scope parameter (e.g. "java") is required for external types.
> - Known limitation (as of typespec-java 0.39.x): The Java emitter does not fully support external types on Enum/Union definitions. It will still generate the class instead of referencing the JDK type. This is tracked as a bug. Only use Form B for Model types until the emitter is fixed.
> De-duplication use case: Form B can suppress emission of generated models that duplicate an external dependency. For example, @@alternateType(OpenAI.Reasoning, { identity: "com.openai.models.Reasoning" }, "java") prevents the codegen from emitting its own Reasoning class — any property typed as OpenAI.Reasoning will use com.openai.models.Reasoning directly. This works for Model types that are members of unions too (e.g. ComparisonFilter inside a Filters union). See the dedup-openai skill for the full workflow including serialization fixes.
Use when you need to override a single property to an external Java type, but Form B cannot be used because it would change the type globally, and { identity: ... } cannot be applied directly to properties.
The workaround is a two-step indirection: define a dummy model annotated with the external identity, then use @@alternateType on the property pointing to that model.
// Step 1: Define a dummy model with the external Java type identity
@alternateType({ identity: "java.util.TimeZone" }, "java")
model TimeZoneType {}
// Step 2: Override the property type to use the dummy model
@@alternateType(OpenAI.ApproximateLocation.timezone, TimeZoneType, "java");
Why this works: @alternateType({identity: ...}) is supported on Model definitions (Form B). The @@alternateType on a property accepts any TypeSpec type as the alternate (Form A). By combining both, the property override resolves through the model to the external Java class.
What does NOT work (and why this form exists):
{ identity: ... } directly on a property → compiler error / silently ignored.@alternateType({identity: ...}) on a scalar → the Java emitter ignores the identity and falls back to BinaryData (as of typespec-java 0.40.x).@alternateType({identity: ...}) on a scalar extends string → the emitter ignores the identity entirely and uses String.Important: The Java emitter generates writeJsonField / TypeName.fromJson(reader) calls for the overridden property, which will not compile because the external Java type (e.g. java.util.TimeZone) does not implement JsonSerializable. You must fix toJson/fromJson manually — see step 5 below.
Edit the client.java.tsp file inside TempTypeSpecFiles/. Add the decorator(s) under the type-replacement section (usually at the bottom of the file):
// EvaluatorVersion datetime fields are typed as string in the spec
@@alternateType(EvaluatorVersion.created_at, utcDateTime, "java");
Always generate with --save-inputs so the edited TypeSpec files are preserved:
tsp-client generate --save-inputs
After generation, verify the Java source uses the expected type:
grep -n "OffsetDateTime\|DayOfWeek\|<ExpectedType>" src/main/java/com/azure/ai/projects/models/<ModelName>.java
Check that:
private OffsetDateTime createdAt;)public OffsetDateTime getCreatedAt())CoreUtils.parseBestOffsetDateTime)src/main/java/java/)After generation, always write a unit test that verifies the generated model serializes and deserializes the overridden type to the same wire-format values defined in the original TypeSpec. This is critical because the emitter may generate serialization code that does not match the API wire format (e.g. java.time.DayOfWeek.name() produces "MONDAY" but the TypeSpec union defined "Monday").
Place the test class under src/test/java/ in the model's package (e.g. com.azure.ai.projects.models).
The test must cover three scenarios:
"Monday", not UPPER_CASE "MONDAY").Example test skeleton:
@Test
void serializationProducesWireFormatValues() throws IOException {
// Build model with the Java type
var schedule = new WeeklyRecurrenceSchedule(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.FRIDAY));
String json = toJsonString(schedule);
// Assert the wire values match the TSP union/enum values, NOT the Java enum constant names
String expected = "{\"daysOfWeek\":[\"Monday\",\"Friday\"],\"type\":\"Weekly\"}";
assertEquals(expected, json);
}
@Test
void deserializationParsesWireFormatValues() throws IOException {
// Use TSP-defined wire-format values
String json = "{\"daysOfWeek\":[\"Monday\",\"Wednesday\"],\"type\":\"Weekly\"}";
WeeklyRecurrenceSchedule schedule;
try (JsonReader reader = JsonProviders.createReader(json)) {
schedule = WeeklyRecurrenceSchedule.fromJson(reader);
}
assertEquals(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY), schedule.getDaysOfWeek());
}
@Test
void roundTripPreservesValues() throws IOException {
var original = new WeeklyRecurrenceSchedule(Arrays.asList(DayOfWeek.SUNDAY, DayOfWeek.SATURDAY));
String json = toJsonString(original);
WeeklyRecurrenceSchedule deserialized;
try (JsonReader reader = JsonProviders.createReader(json)) {
deserialized = WeeklyRecurrenceSchedule.fromJson(reader);
}
assertEquals(original.getDaysOfWeek(), deserialized.getDaysOfWeek());
}
When the emitter generates incorrect serialization (e.g. element.name() instead of PascalCase), you must manually fix the toJson and fromJson methods in the generated model class:
@Generated annotation from toJson and fromJson. This ensures your customizations survive future tsp-client generate / tsp-client update runs — the codegen will not overwrite methods that lack @Generated.@Generated methods. Javadoc you write above a non-@Generated method will survive, but standalone comments above the signature will be wiped. Place markers like // AI Tooling: ... on the first line inside the method body. For fields, place marker comments on the same line (trailing), not on the line above.java.time.DayOfWeek:toJson: convert DayOfWeek.MONDAY → "Monday" (PascalCase) using a helper like: private static String toPascalCase(DayOfWeek day) {
String name = day.name();
return name.charAt(0) + name.substring(1).toLowerCase(Locale.ROOT);
}
fromJson: convert "Monday" → DayOfWeek.MONDAY by uppercasing before valueOf(): DayOfWeek.valueOf(reader.getString().toUpperCase(Locale.ROOT))
Form C serialization fixes: When using the model indirection (Form C), the emitter generates writeJsonField("field", this.field) and ExternalType.fromJson(reader) — both will fail to compile because the external Java type does not implement JsonSerializable. Fix by:
toJson: replace writeJsonField with the correct writer method (e.g. writeStringField("timezone", this.timezone != null ? this.timezone.getID() : null))fromJson: replace ExternalType.fromJson(reader) with the correct factory (e.g. TimeZone.getTimeZone(reader.getString()))If the user supplied a local checkout path for Azure/azure-rest-api-specs, apply the same edits to the client.java.tsp there. Derive the file path from tsp-location.yaml:
<local_spec_repo>/<directory>/client.java.tsp
For example, if directory: specification/ai-foundry/data-plane/Foundry and the local repo is at ~/code/azure-rest-api-specs:
~/code/azure-rest-api-specs/specification/ai-foundry/data-plane/Foundry/client.java.tsp
Verify the file exists before editing. If it doesn't, warn the user and print the expected path.
After confirming the generated code is correct, remind the user:
> The @@alternateType changes in client.java.tsp are local overrides in TempTypeSpecFiles/.
> For these to persist across future code generations, the same changes must be contributed to the
> Azure/azure-rest-api-specs repository via a pull request targeting the corresponding
> client.java.tsp file under the specification/ directory.
>
> Build the PR URL from tsp-location.yaml:
> - Repo: repo field (e.g. Azure/azure-rest-api-specs)
> - Directory: directory field (e.g. specification/ai-foundry/data-plane/Foundry)
> - File: client.java.tsp in that directory
| Symptom | Cause | Fix |
|---------|-------|-----|
| Generated class still uses String | Decorator not picked up | Verify the model/field names match exactly (case-sensitive, use the TypeSpec name, not the Java name) |
| File generated under src/main/java/java/time/... | External type identity used on an Enum/Union | Remove the decorator — this is the known emitter bug for Enum/Union external types |
| Compiler error on @@alternateType | Wrong target kind | External types must target type definitions, not properties. TypeSpec built-ins can target properties. |
| Warning: external-type-on-model-property | External type { identity: ... } applied to a property | Move the decorator to the type definition instead |
| Property becomes BinaryData instead of external type | @alternateType({identity: ...}) used on a scalar | Scalars don't support external identity resolution. Use Form C (model indirection) instead. |
| writeJsonField / fromJson compile errors after Form C | Emitter treats external type as JsonSerializable | Remove @Generated from toJson/fromJson and fix serialization manually (see step 5). |
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take azure/tsp-type-override from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.