TL;DR: your first example fails to crash because you're interfering with the versioning feature in the wrong way...
Although the accepted answer is broadly correct, it does get a few points of order incorrect, and I thought it would be worth adding a few clarifications. I think it's quite important to understand understand what is happening under the hood with optimistic locking, hence my pedantry here.
From the accepted answer: "Your top example works because you only have the one instance, so it's state can be transparently monitored without the need for locking"
Firstly, "optimistic locking" is a misnomer; there's no locking going on anywhere in the process (it's just versioning - pessimistic locking does use locks - it's confusing) so the handwavy "can be transparently monitored" makes the underlying process sound a lot more complicated than it is.
What is really happening: when an update is issued, Hibernate includes the version number of the object from when it was first loaded in this transaction into the resulting update statement. If zero rows are updated, the OptimisticLockException is thrown.
So that answers why your first example runs without error...
def instance = Group.findByID(49)
results in:
select * from tbl_group where id=49
Let's say the version was 28. Hibernates keeps a copy of all of the properties of an object when it is loaded - it does this so it can do a dirty check when the transaction is committed - if none of the values have changed, it doesn't need to do an update.
So this line of code has no effect on the version number that hibernate will use in the update:
instance.version = 5 // something smaller than the current
When you eventually call save:
instance.save flush:true, failOnError: true
This will result in
update tbl_group set version=**29** where id=49 **and version=28**
(BTW The logic in Hibernate is if this update fails to update any rows, then another transaction must have modified that row and incremented its version number.)
So that's why you're seeing no exception, the version used was the version number originally read in by this transaction, not the version number that you artificially wrote into the object.
A better test of this functionality would be to pause the execution just before the save() is executed (eg using a debugger). Now go into the database and change the version column for that row (to a higher number). Now the save() will fail with the OptimisticLockException because Hibernate thinks that another transaction modified that row first.
[I'd like to answer why the second example DID throw an exception, but I'd need to know how you did the copy (did all members including the ID copy over?) and did it definitely fail with OptimisticLockException or something else? Whatever reason, it's a weird set of operations that aren't realistic or illustrative so although it might be a good intellectual exercise to figure out what's happening, I'll leave that to others!]
Edit to add: I've run a test and the second example does indeed throw an OptimisticLockException - BUT it's at the merge() BEFORE you even change the version number. So you're getting the right outcome for the wrong reason. I would investigate further but it's an old question and I doubt anyone cares - but my rough feeling is that by copying the object and trying to have two objects representing the same persistent entity, the entitymanager is getting confused and raising it as an optimistic lock exception. I'm sure there's a fuller explanation (it's something to do with the select that is issued as step 1 of the merge) and I might return to that another day. Basically, Hibernate will do odd things if you give it odd things to do!