GradleException does not stop the build

I have the following code

class PublishReleaseTask extends DefaultTask {
...
@TaskAction
    public void action(){
        if(!project.hasProperty('RELEASE_NAME')){
            throw new GradleException('project should have RELEASE_NAME property')
        }
    }
}

The PublishReleaseTask (named publishIvyRelease in my case) is finalized by other tasks (generated by the ivy publish plugin) :
generateDescriptorFileForIvyPublication and publishIvyPublicationToReleaseRepository

If my GradleException is thrown, I would have expected that the build stops immediately. This is not the case, as shown by the following traces:

03:34:43 :publishIvyRelease
03:34:43
03:34:43 :publishIvyRelease FAILED
03:34:43 :generateDescriptorFileForIvyPublication
03:34:43
03:34:43 :publishIvyPublicationToReleaseRepository
03:34:43
03:34:43 Upload xxx/unspecified/yyy-unspecified.jar
03:34:43
03:34:43 FAILURE: Build failed with an exception.
03:34:43
03:34:43 * What went wrong:
03:34:43 Execution failed for task ‘:publishIvyRelease’.
03:34:43 > project should have RELEASE_NAME property

Finalizer tasks are executed even when the finalized task fails (think try { } finally { }). If this is not the behavior you want, you’ll want to use a different type of task relationship.

https://docs.gradle.org/current/userguide/more_about_tasks.html#N10F3E

There is no task relation ship to tell
"task X, if executed, must be followed by task Y"

You can say task Y, if executed, must be preceded by task X (with dependsOn)
You can say task Y must run after task X, or should run after task X, but you need to add task Y yourself on the task list to be executed

isn’t

`x.finalizedBy y 

what you’re looking for?

Well, that’s the point of my question.
When task X finishes with an exception, I don’t want task Y to be run.
When using X.finalizedBy Y, Y will be called no matter what, as pointed out by @mark_vieira

The goal of finalizedBy is definitely to be executed independently of the fact a task has completed successfully or not, so that is definitely not what you want. In theory you should have the “finalizer” task depend on the other one and execute the finalizer, but for your use case you can declare that the finalizing task should be executed only if the other one is successful:

task fails << { throw new RuntimeException('fails') }

task finalizer {
  onlyIf { !fails.state.failure }
  doLast { println 'executing finalizer' }
}
fails.finalizedBy finalizer

The finalizedBy relationship is very often misused. It’s intended purpose is to be run after a task regardless of its successfulness to do things like resource cleanup. It is more often the case that the more appropriate action is to introduce an additional task. So rather than A ‘finalized by’ B, we would say task C ‘depends on’ B and B ‘depends on’ A.

Yeah, I see your point.
That’s generally how it works in ant too, not the way around.
I’m going to change my logic to use the proper and intended way with ‘depends on’