BoundaryML/baml · error
Expected a null value
Error message
Expected a null value
What it means
ValueExpr::resolve_null asserts that an expression resolves to ResolvedValue::Null, used when config explicitly expects a null value. Any other resolved variant (or a resolution failure) produces the opaque 'Expected a null value' error, hiding the real cause.
Solutions
- Resolve with resolve() first to check the actual variant instead of asserting null blindly.
- If the value should be null, remove or unset the config field / env var so it resolves to Null.
- If 'null' comes from a string literal, use JSON null in config rather than the quoted string "null".
- Match on ResolvedValue yourself and treat Null as one case rather than erroring.
- Add an explicit default of null in the config schema if the field is optional.
Example fix
// before (config sets value: "null" as a string) expr.resolve_null(&ctx)?; // Err: Expected a null value // after // config: value null (unquoted JSON null) expr.resolve_null(&ctx)?;
Defensive patterns
Strategy: type-guard
Validate before calling
let resolved = expr.resolve(&ctx)?;
if !matches!(resolved, ResolvedValue::Null(_)) {
bail!("expected null, got: {resolved:?}");
} Type guard
fn is_null(v: &ResolvedValue) -> bool { matches!(v, ResolvedValue::Null(..)) } Try / catch
if let Err(e) = expr.resolve_null(&ctx) {
log::debug!("value present, not null: {e}");
} Prevention
- Use unquoted JSON null in config, not the string "null".
- Unset optional fields rather than assigning placeholder values.
- Match on ResolvedValue instead of asserting a single variant when the value is optional.
- Document which fields are nullable in the config schema.
When it happens
Trigger: Calling resolve_null on an expression that resolves to any concrete value — a string, number, array, or map — e.g. code that treats an optional field as null when the user actually supplied a value.
Common situations: Optional config fields where the developer assumed the default was null but a value was set; env var interpolation that turned 'null' into the string "null" rather than a real null.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Could not unify Bool with
- Could not unify Float with
- Expected a boolean
- Expected a map
- Expected a numeric value
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/cf6e675861c7e561.
Report an issue: GitHub.
Appendix: source
Thrown at engine/baml-lib/baml-types/src/value_expr.rs:576
pub fn resolve_map(&self, ctx: &impl GetEnvVar) -> Result<IndexMap<String, ResolvedValue>> {
match self.resolve(ctx) {
Ok(ResolvedValue::Map(m, ..)) => Ok(m.into_iter().map(|(k, (_, v))| (k, v)).collect()),
_ => Err(anyhow::anyhow!("Expected a map")),
}
}
pub fn resolve_numeric(&self, ctx: &impl GetEnvVar) -> Result<String> {
match self.resolve(ctx) {
Ok(ResolvedValue::Numeric(n, ..)) => Ok(n),
_ => Err(anyhow::anyhow!("Expected a numeric value")),
}
}
pub fn resolve_null(&self, ctx: &impl GetEnvVar) -> Result<()> {
match self.resolve(ctx) {
Ok(ResolvedValue::Null(..)) => Ok(()),
_ => Err(anyhow::anyhow!("Expected a null value")),
}
}
pub fn resolve_serde<T: serde::de::DeserializeOwned>(&self, ctx: &impl GetEnvVar) -> Result<T> {
let value = self.resolve(ctx)?;
let value: serde_json::Value = value.try_into()?;
match serde_json::from_value(value) {
Ok(v) => Ok(v),
Err(e) => Err(anyhow::anyhow!("Failed to deserialize value: {e}")),
}
}
/// Resolve and deserialize, with support for template_string calls.
pub fn resolve_serde_with_templates<T: serde::de::DeserializeOwned>(
&self,
ctx: &impl GetEnvVar,
template_renderer: &impl TemplateStringRenderer,
) -> Result<T> {View on GitHub (pinned to bd85ce9dee)