I would strictly avoid setters when there may be another more robust possibility:
You may want to provide a setter in case you want to mutate a field.
Issue you mentioned:
What if a developer breaks the validation rules I provided, by
overriding the setter...Should we go with final keyword?
I would say: "Dissuade him to do so".
Indeed, declaring a setter is like saying to the world:
"You can provide me some field value and then you can do your own thing with me !" => procedural code
"Tell! Don't ask" philosophy would say:
"Client, tell me what to do, I will do."
And in general, your main "complex" logic would be in the main public api of the POJO.
I doubt that a developer would be tempted to override the full logic of a POJO without risking complete bad behaviors..
So, for instance, instead of declaring an opened setter to any value, force the value to be passed on the main method, so that you can control the flow:
public void computeWith(int x) {
if(x <= 0) throw new IllegalArgumentException("X must be superior to 0");
//code computing here.
}
You will notice that some fields might even not be needed any more in the POJO with this way of doing.
To sum up: It's easy to override a poor validation rule...but risky for a behaviour.
Just my 2 cents.