Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Function

throw

throw new <ExceptionType>("<message>");

Raises a fault. It ends the function immediately, and the rows the function wrote are discarded rather than half-written. The exception types are a closed set of four, plus the base Exception — you do not declare your own.

stable4 examples compiled by CIfunctionerrors

Summary#

throw raises a fault. The function stops where it stands, and the rows it wrote are discarded — a function that throws leaves nothing half-written behind it.

You throw one of a closed set of types. There is no class MyException to declare:

entity Order {
  [Required, Unique, MaxLength(20)] string Code;
  decimal Total;

  // Every entity states who may touch it — with no `security { }` block it is denied to everyone.
  // A real app scopes these grants to a user; an example still has to declare them, because an example
  // that could not actually run is not an example. See the security guide.
  security { allow create, read, update when IsAuthenticated || IsAnonymous; }
}

void Refund(string code, decimal amount) {
  var order = Order.SingleOrDefault(o => o.Code == code);
  if (order == null) { throw new NotFoundException($"no order '{code}'"); }
  if (amount > order.Total) { throw new ValidationException("a refund cannot exceed the order total"); }

  order.Total = order.Total - amount;
}

Signature#

throw new Exception("<message>");                  // the base type — the catch-all
throw new NotFoundException("<message>");          // asked for a thing that is not there
throw new ValidationException("<message>");        // the input or the resulting row is not acceptable
throw new ConflictException("<message>");          // it clashes with the current state of the data
throw new OAuthConnectionFailedException("<msg>"); // an external authorization handshake failed

Description#

The five types, and nothing else#

The vocabulary is closed. Naming an unknown type is a compile error that lists the ones you may use, so you cannot misspell your way into a silent catch-all:

TypeWhat it meansAlso raised for you by
Exceptionthe base. Every other type is one, so catch (Exception e) catches everything
NotFoundExceptionyou asked for something that does not exist
ValidationExceptionthe input, or the row you are about to write, is not acceptablea broken [[entity-constraints|constraint]] or [[entity-invariants|invariant]]
ConflictExceptionit cannot be done given the current state of the datatwo writers colliding on the same rows
OAuthConnectionFailedExceptionan external authorization handshake failedthe OAuth surface

The point of the closed set is that a catch has a finite, knowable set of things to match on, and the two types the platform raises on your behalf are in it. That is what lets you catch a constraint violation as an ordinary ValidationException (try / catch / finally) rather than by inspecting a database error.

Why you cannot declare your own: a fault's type is what a catch matches on, and its message is what a human reads. A bespoke type would carry no more information than the message already does, and it would not survive leaving the app — see below.

A throw discards what the function wrote#

This is the part worth internalising. A function is transactional (function), and a throw is the abort:

void PlaceTwo(string first, string second) {
  var a = new Order { Code = first, Total = 10m };

  if (second == "") { throw new ValidationException("the second code is required"); }

  var b = new Order { Code = second, Total = 20m };
}

If second is empty, neither order exists. The first new Order was already written in the ordinary sense — it is simply never committed. You do not unwind it, and there is no state in which the caller can observe it.

Proved, not asserted:

[Test]
void A_throw_discards_the_rows_the_function_had_written() {
  Assert.Throws<ValidationException>(() => PlaceTwo("A1", ""));

  Assert.Empty(Order.ToList());   // NOT one row — the first `new Order` went with it
}

[Test]
void The_type_is_what_a_caller_matches_on() {
  Assert.Throws<NotFoundException>(() => Refund("nope", 1m));
}

throw ends a path#

A throw satisfies the compiler's "all paths return a value" rule, exactly as it does in C#. A guard clause that throws needs no else:

decimal TotalOf(string code) {
  var order = Order.SingleOrDefault(o => o.Code == code);
  if (order == null) { throw new NotFoundException($"no order '{code}'"); }

  return order.Total;   // reachable only when the order exists — no `else`, no null check
}

What a caller outside the app sees#

Inside the app, a throw is caught by type (try / catch / finally).

A function reached from outside the app — over its published REST surface, or as a tool call — is different: a fault becomes a failure carrying your message, and the type does not survive the crossing. So write the message for the person who will read it, and do not expect an external caller to branch on NotFoundException versus ValidationException. Inside, the type is everything; at the edge, the message is.

See also#

  • try / catch / finally — catching one, and what else can throw
  • function — why a fault discards the writes: a function is one transaction
  • constraints · invariant — the rules that raise ValidationException for you
  • AssertAssert.Throws<T>, which is how you prove a function refuses what it should

Related

try / catch / finally

Handles a fault. C#'s syntax, including typed catches, catch filters and finally. The rows written inside a try block…

function

A function is a top-level unit of work, written like a C# method — a return type, a name, typed parameters, a body. It…

constraints

The per-member rules the database enforces — Required, Unique, MaxLength/MinLength, Min/Max, Pattern, and the…

invariant

A row-level rule spanning several members, checked when the row is written. Use it when a constraint on one member is…

Assert

The assertions a test makes. Beyond the usual equality and null checks there are comparisons (Greater, Less, InRange)…