Note: I haven't tested this in Java, but this does work with the pcre engine.
^(?=.{8,20}$)(?=[^A-Z]*?[A-Z])(?=[^a-z]*?[a-z])(?=[^0-9]*?[0-9])(?=[^!@#$%^&*]*?[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]+$
Broken down:
^(?=.{8,20}$) //this matches 8 to 20 characters, inclusive (positive lookahead)
(?=[^A-Z]*?[A-Z]) //this matches if one uppercase letter is present (positive lookahead)
(?=[^a-z]*?[a-z]) //this matches if one lowercase letter is present (positive lookahead)
(?=[^0-9]*?[0-9]) //this matches if one digit is present (positive lookahead)
(?=[^!@#$%^&*]*?[!@#$%^&*]) //this matches if one special character is present (positive lookahead)
[a-zA-Z0-9!@#$%^&*]+$ //this matches if the enclosed characters are present
This may be a bit over the top for what you want but I have tested it here:
https://regex101.com/r/P0J4X8/1
Also, the lazy evaluators may be unnecessary in the lookaheads