Apex like java it's built upon has exceptions as the core way of dealing with, er, exceptional situations. Apex exceptions have always had a couple oddities that I started poking deeper into.

The first sign Exception types are different than any other apex class is that their naming convention is enforced by the compiler itself. You'll get a compilation failure if you try something like

But the rabbit hole goes much deeper. Try and do something totally crazy like define a no-args constructor for our exception type:

That's... odd. Defining a no-args constructors anywhere else in apex is perfectly valid, and in the case of global classes very wise (since if you define another constructor the implicit global one goes away. That was a fun week of compile failures). Let's try doing something else, like defining a constructor that takes a message argument:

Same thing again. Odd. Well we can always do something hacky like define another constructor with a boolean arg that is just ignored. Works well enough, although it's a bit of an eyebrow-raiser in code review. Maybe there's one last thing we can try though, how about overriding setMessage to do our boilerplate concatenation.

Oh, how silly, I forgot the override keyword since we're overriding a superclass's method! When we add that things should all work out!

Oh. Wait, but what? You only get the message about needing override if you're overriding a superclass method, but simultaneously the compiler denies there being a superclass?

I don't have any way of telling for sure, but I have to hypothesize that this is done as a security measure. Exceptions would seem like a great target to use if you wanted to break out of the apex runtime's sandbox (they're mentioned it compiles apex to java bytecode now, which would imply it's executed via reflection), so they appear to have taken a sensible approach of not exposing anything about java exception types within apex.

One last thing to really break your brain. If we define a virtual exception type and extend that we can override setMessage, it just doesn't get invoked by the template-injected constructors we just tried, and failed, to make.

What extending exception in apex seems to do then is not subclass anything like you'd expect, but instead apply a pre-built template. What else it does besides constructors I have no idea, but it just goes to prove the old adages about non-trivial abstractions true. I really can't complain either, the apex team had to figure out some pretty tough engineering problems and if this is the worst (non-intentional) oddity in the class system I'll take it.