Simply add nested capturing groups to keep the chars validation not strictly as a sequence. The regex can be also simplified as follow (without extra groups):
(?=(?:.*[0-9]){2,20})
(?=(?:.*[a-z]){2,20})
(?=(?:.*[A-Z]){3,20})
(?=(?:.*[@#$%!&*?_~,-]){2,20})
.{9,20}
[^<>'\"] # This matches also the newline char, i don't think you really want this...
In java use it as follows to match the :
String regex = "(?=(?:.*[0-9]){2,20})(?=(?:.*[a-z]){2,20})(?=(?:.*[A-Z]){3,20})(?=(?:.*[@#$%!&*?_~,-]){2,20}).{9,20}[^<>'\"]";
String password = "23aaA@AA!@X"; // This have to be 10 chars long at least, no newline
if (password.matches(regex))
System.out.println("Success");
else
System.out.println("Failure");
The regex requires a password with (all not strictly in sequence):
(?=(?:.*[0-9]){2,20}): 2 numbers
(?=(?:.*[a-z]){2,20}): 3 lowercase lettes
(?=(?:.*[A-Z]){3,20}): 3 uppercase letters
(?=(?:.*[@#$%!&*?_~,-]){2,20}): 2 of the symbols in the chars group
.{9,20}: Min length of 9 and max of 20
[^<>'\"]: One char that is not in (<,>,',") (NOTE: this matches also the newline)
So the min/max is actually 10/21 but the last statemente matches also the newline, so in the online regex demo, the visible chars will be between 9 and 20.
Regex online demo here
password.matches("[A-Z]") && password.matches("[a-z]") && password.matches("\d") && password.matches("[^A-Za-z\d]")?(?=(?:.*[0-9]){2,20})(?=(?:.*[a-z]){2,20})(?=(?:.*[A-Z]){3,20})(?=(?:.*[@#$%!&*?_~,-]){2,20}).{9,20}[^<>'\"].