There are some really quick filters that you can run before the "main test" that quickly detect a large number of non-squares, and some quick "reductions" that make the number smaller while ensuring that its "squareness" property does not change (this is useful since here the square root itself is not needed, we only need to know whether it exists).
For example, given a number n=m*4k we can get rid of the 4k part since either n is a square and its square root is n*2k or it isn't and that must mean m is not a square because the 4k part cannot cause non-square-ness.
So, the powers of 4 can be divided out, for example:
n >>= Long.numberOfTrailingZeros(n) & -2;
& -2 rounds down the number of trailing zeros to an even number, so the shift only removes powers of 4.
Then, with the pre-condition that
x is odd (if the powers of 4 are removed from
n then the powers of 2 are removed from its square root .. unless
n = 0 but you can add
if (n == 0) return true;) there are much fewer than 8 possible outcomes for
(x * x) % 8, namely only 1: 1. That gives a really efficient filter:
if ((n & 7) != 1)
This combination of tricks can be prepended to any "perfect square test" in order to run the main test only about 1/8th of the time assuming uniform random input, so together they can be used to make anything faster unless it is expected that most inputs are actually squares.