Summary
The public API of this SDK uses nullable boxed types (Boolean, Integer, Double) on mutable config/builder classes to represent optional values. This is a C# idiom — not a Java one. Java has had Optional<T>, OptionalInt, and OptionalDouble since Java 8 (2014), and returning Optional from getters is the established idiomatic way to express "this value may be absent."
Today the SDK has ~30 public getters on mutable classes returning nullable boxed types and zero uses of Optional anywhere in non-generated source code. This is a clear signal that the nullable-return convention was carried over from the C# reference implementation rather than adapted to Java idioms during the port.
Scope
What to change
Only mutable config/builder classes (regular classes with hand-written getters/setters). These classes all have tri-state semantics where null means "use the server/runtime default" — a contract that is invisible in the current type signatures but becomes explicit with Optional.
What NOT to change
- Record types — Record accessor methods are auto-generated from components.
Optional as a record component is unidiomatic and clashes with Jackson deserialization. Records like PingResponse, ToolDefinition, PreToolUseHookOutput, PostToolUseHookOutput, SessionEndHookOutput, and UserPromptSubmittedHookOutput are explicitly out of scope.
- Generated code under
src/generated/java/ — do not modify.
- Internal fields — Fields remain as nullable boxed types for Jackson serialization (
@JsonInclude(NON_NULL)). Only the getter return types and setter parameter types change.
Specification
For each affected getter/setter pair, apply this transformation:
For Boolean fields
Before:
private Boolean enableSessionTelemetry;
public Boolean getEnableSessionTelemetry() {
return enableSessionTelemetry;
}
public SessionConfig setEnableSessionTelemetry(Boolean enableSessionTelemetry) {
this.enableSessionTelemetry = enableSessionTelemetry;
return this;
}
After:
private Boolean enableSessionTelemetry;
public Optional<Boolean> getEnableSessionTelemetry() {
return Optional.ofNullable(enableSessionTelemetry);
}
public SessionConfig setEnableSessionTelemetry(boolean enableSessionTelemetry) {
this.enableSessionTelemetry = enableSessionTelemetry;
return this;
}
public SessionConfig clearEnableSessionTelemetry() {
this.enableSessionTelemetry = null;
return this;
}
For Integer fields
Before:
private Integer sessionIdleTimeoutSeconds;
public Integer getSessionIdleTimeoutSeconds() {
return sessionIdleTimeoutSeconds;
}
public CopilotClientOptions setSessionIdleTimeoutSeconds(Integer sessionIdleTimeoutSeconds) {
this.sessionIdleTimeoutSeconds = sessionIdleTimeoutSeconds;
return this;
}
After:
private Integer sessionIdleTimeoutSeconds;
public OptionalInt getSessionIdleTimeoutSeconds() {
return sessionIdleTimeoutSeconds == null ? OptionalInt.empty() : OptionalInt.of(sessionIdleTimeoutSeconds);
}
public CopilotClientOptions setSessionIdleTimeoutSeconds(int sessionIdleTimeoutSeconds) {
this.sessionIdleTimeoutSeconds = sessionIdleTimeoutSeconds;
return this;
}
public CopilotClientOptions clearSessionIdleTimeoutSeconds() {
this.sessionIdleTimeoutSeconds = null;
return this;
}
For Double fields
Same pattern, using OptionalDouble and primitive double setter.
Affected classes and fields
Public API config classes (user-facing)
CopilotClientOptions.java
| Getter |
Current return |
New return |
Setter param |
getSessionIdleTimeoutSeconds() |
Integer |
OptionalInt |
int |
getUseLoggedInUser() |
Boolean |
Optional<Boolean> |
boolean |
SessionConfig.java
| Getter |
Current return |
New return |
Setter param |
getEnableSessionTelemetry() |
Boolean |
Optional<Boolean> |
boolean |
getEnableConfigDiscovery() |
Boolean |
Optional<Boolean> |
boolean |
getIncludeSubAgentStreamingEvents() |
Boolean |
Optional<Boolean> |
boolean |
ResumeSessionConfig.java
| Getter |
Current return |
New return |
Setter param |
getEnableSessionTelemetry() |
Boolean |
Optional<Boolean> |
boolean |
getEnableConfigDiscovery() |
Boolean |
Optional<Boolean> |
boolean |
getIncludeSubAgentStreamingEvents() |
Boolean |
Optional<Boolean> |
boolean |
InfiniteSessionConfig.java
| Getter |
Current return |
New return |
Setter param |
getEnabled() |
Boolean |
Optional<Boolean> |
boolean |
getBackgroundCompactionThreshold() |
Double |
OptionalDouble |
double |
getBufferExhaustionThreshold() |
Double |
OptionalDouble |
double |
InputOptions.java
| Getter |
Current return |
New return |
Setter param |
getMinLength() |
Integer |
OptionalInt |
int |
getMaxLength() |
Integer |
OptionalInt |
int |
ModelCapabilitiesOverride.Supports (inner class)
| Getter |
Current return |
New return |
Setter param |
getVision() |
Boolean |
Optional<Boolean> |
boolean |
getReasoningEffort() |
Boolean |
Optional<Boolean> |
boolean |
ModelCapabilitiesOverride.Limits (inner class)
| Getter |
Current return |
New return |
Setter param |
getMaxPromptTokens() |
Integer |
OptionalInt |
int |
getMaxOutputTokens() |
Integer |
OptionalInt |
int |
getMaxContextWindowTokens() |
Integer |
OptionalInt |
int |
ProviderConfig.java
| Getter |
Current return |
New return |
Setter param |
getMaxPromptTokens() |
Integer |
OptionalInt |
int |
getMaxOutputTokens() |
Integer |
OptionalInt |
int |
TelemetryConfig.java
| Getter |
Current return |
New return |
Setter param |
getCaptureContent() |
Boolean |
Optional<Boolean> |
boolean |
SessionUiCapabilities.java
| Getter |
Current return |
New return |
Setter param |
getElicitation() |
Boolean |
Optional<Boolean> |
boolean |
CustomAgentConfig.java
| Getter |
Current return |
New return |
Setter param |
getInfer() |
Boolean |
Optional<Boolean> |
boolean |
UserInputRequest.java
| Getter |
Current return |
New return |
Setter param |
getAllowFreeform() |
Boolean |
Optional<Boolean> |
boolean |
Internal wire DTOs (not directly user-facing, but same pattern applies)
CreateSessionRequest.java
| Getter |
Current return |
New return |
Setter param |
getEnableSessionTelemetry() |
Boolean |
Optional<Boolean> |
boolean |
getRequestPermission() |
Boolean |
Optional<Boolean> |
boolean |
getRequestUserInput() |
Boolean |
Optional<Boolean> |
boolean |
getHooks() |
Boolean |
Optional<Boolean> |
boolean |
getStreaming() |
Boolean |
Optional<Boolean> |
boolean |
getEnableConfigDiscovery() |
Boolean |
Optional<Boolean> |
boolean |
getIncludeSubAgentStreamingEvents() |
Boolean |
Optional<Boolean> |
boolean |
getRequestElicitation() |
Boolean |
Optional<Boolean> |
boolean |
ResumeSessionRequest.java
| Getter |
Current return |
New return |
Setter param |
getEnableSessionTelemetry() |
Boolean |
Optional<Boolean> |
boolean |
getRequestPermission() |
Boolean |
Optional<Boolean> |
boolean |
getRequestUserInput() |
Boolean |
Optional<Boolean> |
boolean |
getHooks() |
Boolean |
Optional<Boolean> |
boolean |
getEnableConfigDiscovery() |
Boolean |
Optional<Boolean> |
boolean |
getDisableResume() |
Boolean |
Optional<Boolean> |
boolean |
getStreaming() |
Boolean |
Optional<Boolean> |
boolean |
getIncludeSubAgentStreamingEvents() |
Boolean |
Optional<Boolean> |
boolean |
getRequestElicitation() |
Boolean |
Optional<Boolean> |
boolean |
McpServerConfig.java (abstract base)
| Getter |
Current return |
New return |
Setter param |
getTimeout() |
Integer |
OptionalInt |
int |
ModelLimits.java
| Getter |
Current return |
New return |
Setter param |
getMaxPromptTokens() |
Integer |
OptionalInt |
int |
Caller updates required
All internal callers that null-check these getters must be updated to use Optional APIs. Common patterns:
// BEFORE (e.g., CliServerManager.java)
if (options.getSessionIdleTimeoutSeconds() != null) {
args.add("--session-idle-timeout");
args.add(String.valueOf(options.getSessionIdleTimeoutSeconds()));
}
// AFTER
options.getSessionIdleTimeoutSeconds().ifPresent(timeout -> {
args.add("--session-idle-timeout");
args.add(String.valueOf(timeout));
});
// BEFORE (e.g., CliServerManager.java)
if (options.getUseLoggedInUser() != null && options.getUseLoggedInUser()) {
args.add("--use-logged-in-user");
}
// AFTER
options.getUseLoggedInUser().filter(v -> v).ifPresent(v ->
args.add("--use-logged-in-user")
);
// BEFORE (e.g., SessionRequestBuilder.java)
Boolean value = config.getEnableSessionTelemetry();
if (value != null) {
request.setEnableSessionTelemetry(value);
}
// AFTER
config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry);
Jackson serialization
No changes needed to Jackson configuration. The internal fields remain as nullable boxed types (Boolean, Integer, Double) with @JsonInclude(NON_NULL). Jackson reads/writes the fields directly — the Optional wrapper exists only on the getter return type, not on the field or in the serialized JSON.
Testing
- All existing tests must continue to pass after the changes.
- The
clearXxx() methods should be exercised in tests to verify they reset the field to null (and that Jackson then omits the field from serialized output).
- Callers updated from null-check patterns to
Optional patterns should produce identical behavior.
Why this matters
| Dimension |
Current (nullable boxed) |
Proposed (Optional) |
| Nullability visible in type signature? |
No |
Yes |
| Forgotten null-check consequence |
Compiles, NPEs at runtime |
Does not compile |
| Idiomatic Java? |
No (C# convention) |
Yes (standard since Java 8) |
Optional usage in SDK today |
0 instances |
— |
| Records affected? |
— |
None (explicitly excluded) |
This is a breaking API change for getter return types and should be planned accordingly.
Summary
The public API of this SDK uses nullable boxed types (
Boolean,Integer,Double) on mutable config/builder classes to represent optional values. This is a C# idiom — not a Java one. Java has hadOptional<T>,OptionalInt, andOptionalDoublesince Java 8 (2014), and returningOptionalfrom getters is the established idiomatic way to express "this value may be absent."Today the SDK has ~30 public getters on mutable classes returning nullable boxed types and zero uses of
Optionalanywhere in non-generated source code. This is a clear signal that the nullable-return convention was carried over from the C# reference implementation rather than adapted to Java idioms during the port.Scope
What to change
Only mutable config/builder classes (regular classes with hand-written getters/setters). These classes all have tri-state semantics where
nullmeans "use the server/runtime default" — a contract that is invisible in the current type signatures but becomes explicit withOptional.What NOT to change
Optionalas a record component is unidiomatic and clashes with Jackson deserialization. Records likePingResponse,ToolDefinition,PreToolUseHookOutput,PostToolUseHookOutput,SessionEndHookOutput, andUserPromptSubmittedHookOutputare explicitly out of scope.src/generated/java/— do not modify.@JsonInclude(NON_NULL)). Only the getter return types and setter parameter types change.Specification
For each affected getter/setter pair, apply this transformation:
For
BooleanfieldsBefore:
After:
For
IntegerfieldsBefore:
After:
For
DoublefieldsSame pattern, using
OptionalDoubleand primitivedoublesetter.Affected classes and fields
Public API config classes (user-facing)
CopilotClientOptions.javagetSessionIdleTimeoutSeconds()IntegerOptionalIntintgetUseLoggedInUser()BooleanOptional<Boolean>booleanSessionConfig.javagetEnableSessionTelemetry()BooleanOptional<Boolean>booleangetEnableConfigDiscovery()BooleanOptional<Boolean>booleangetIncludeSubAgentStreamingEvents()BooleanOptional<Boolean>booleanResumeSessionConfig.javagetEnableSessionTelemetry()BooleanOptional<Boolean>booleangetEnableConfigDiscovery()BooleanOptional<Boolean>booleangetIncludeSubAgentStreamingEvents()BooleanOptional<Boolean>booleanInfiniteSessionConfig.javagetEnabled()BooleanOptional<Boolean>booleangetBackgroundCompactionThreshold()DoubleOptionalDoubledoublegetBufferExhaustionThreshold()DoubleOptionalDoubledoubleInputOptions.javagetMinLength()IntegerOptionalIntintgetMaxLength()IntegerOptionalIntintModelCapabilitiesOverride.Supports(inner class)getVision()BooleanOptional<Boolean>booleangetReasoningEffort()BooleanOptional<Boolean>booleanModelCapabilitiesOverride.Limits(inner class)getMaxPromptTokens()IntegerOptionalIntintgetMaxOutputTokens()IntegerOptionalIntintgetMaxContextWindowTokens()IntegerOptionalIntintProviderConfig.javagetMaxPromptTokens()IntegerOptionalIntintgetMaxOutputTokens()IntegerOptionalIntintTelemetryConfig.javagetCaptureContent()BooleanOptional<Boolean>booleanSessionUiCapabilities.javagetElicitation()BooleanOptional<Boolean>booleanCustomAgentConfig.javagetInfer()BooleanOptional<Boolean>booleanUserInputRequest.javagetAllowFreeform()BooleanOptional<Boolean>booleanInternal wire DTOs (not directly user-facing, but same pattern applies)
CreateSessionRequest.javagetEnableSessionTelemetry()BooleanOptional<Boolean>booleangetRequestPermission()BooleanOptional<Boolean>booleangetRequestUserInput()BooleanOptional<Boolean>booleangetHooks()BooleanOptional<Boolean>booleangetStreaming()BooleanOptional<Boolean>booleangetEnableConfigDiscovery()BooleanOptional<Boolean>booleangetIncludeSubAgentStreamingEvents()BooleanOptional<Boolean>booleangetRequestElicitation()BooleanOptional<Boolean>booleanResumeSessionRequest.javagetEnableSessionTelemetry()BooleanOptional<Boolean>booleangetRequestPermission()BooleanOptional<Boolean>booleangetRequestUserInput()BooleanOptional<Boolean>booleangetHooks()BooleanOptional<Boolean>booleangetEnableConfigDiscovery()BooleanOptional<Boolean>booleangetDisableResume()BooleanOptional<Boolean>booleangetStreaming()BooleanOptional<Boolean>booleangetIncludeSubAgentStreamingEvents()BooleanOptional<Boolean>booleangetRequestElicitation()BooleanOptional<Boolean>booleanMcpServerConfig.java(abstract base)getTimeout()IntegerOptionalIntintModelLimits.javagetMaxPromptTokens()IntegerOptionalIntintCaller updates required
All internal callers that null-check these getters must be updated to use
OptionalAPIs. Common patterns:Jackson serialization
No changes needed to Jackson configuration. The internal fields remain as nullable boxed types (
Boolean,Integer,Double) with@JsonInclude(NON_NULL). Jackson reads/writes the fields directly — theOptionalwrapper exists only on the getter return type, not on the field or in the serialized JSON.Testing
clearXxx()methods should be exercised in tests to verify they reset the field to null (and that Jackson then omits the field from serialized output).Optionalpatterns should produce identical behavior.Why this matters
Optional)Optionalusage in SDK todayThis is a breaking API change for getter return types and should be planned accordingly.