Prime numbers that are also a prime number when reversed This is a challenge on Codewars I took years ago. Task is to find all prime numbers that are also a prime number when reversed, i.e. 13 -&gt; 31 is such a number.
This is still the answer with the most upvotes in the categories 'Best practice' as well as in 'Clever'.
Any improvements or thoughts about this piece of code? Please keep in mind in code challenges like this I always try to code as concise as possible.
Examples in =&gt; output :
    backwardsPrime(2, 100) =&gt; [13, 17, 31, 37, 71, 73, 79, 97] 
    backwardsPrime(9900, 10000) =&gt; [9923, 9931, 9941, 9967] 
    backwardsPrime(501, 599) =&gt; []

code:
public class BackWardsPrime {

    public static String backwardsPrime(long start, long end) {
        StringBuilder sb = new StringBuilder();
        while(start &lt;= end){
            long rev = Long.parseLong(new StringBuilder(
            String.valueOf(start)).reverse().toString());
            if(start &gt; 12 &amp;&amp; isPrime(rev) &amp;&amp; isPrime(start) &amp;&amp; start != rev) 
                sb.append(start + &quot; &quot;);  
            start++;
        }
        return sb.toString().trim();
    }
    
    static boolean isPrime(long n) {
        if(n % 2 == 0) return false;
        for(int i = 3; i * i  &lt;= n  ; i += 2) {
            if(n % i == 0)
                return false;
        }
        return true;
    }
}
Name Classes properly.
What is a &quot;Back Wards Prime&quot;?  I have no idea.  I can guess what a Backward Prime is; perhaps you need to change the capitalization of the class!
I'm told that name is dictated by the coding challenge, so you can't change it for your challenge submission; but I'm leaving this comment stand -- the required class name is horrible.
Name variables properly.
This condition is confusing:
    if ( ... &amp;&amp; start != rev)

Is this really testing if the reversed value is different from the starting value?  No!  You're using the starting value as the loop variable, and continuously incrementing it with start++;.  Make a new loop variable, and use a proper loop.
    for(long candidate = start; candidate &lt;= end; candidate++) {
        ...
    }  

Reducing the work
You appear to have determined that any single digit prime, when read backwards, will not be different than the original, as well as 10 &amp; 12 not being prime, and 11 not being different when reversed.  Therefore, you need only consider values greater than 12.  This optimization should be done before the loop starts,
to avoid the overhead of testing the condition at each iteration.
You should also notice that 2 is the only even prime number, therefore you need not look at any even value.  Instead of moving the start value up by 1, you could skip even values by incrementing by 2.
    if (start &lt; 13)
        start = 13;
    else if (start % 2 == 0)
        start += 1

    for(long candidate = start; candidate &lt;= end; candidate += 2) {
        ...
    }

Avoid creating unnecessary intermediate results
Consider:
new StringBuilder(String.valueOf(start))

You are passing an integer to function which converts it into a string, and then returns that string.  How does that function work?  You then take that resulting string, and pass it to the constructor of the StringBuilder, to copy into the working area.  Do you need that intermediate string?
new StringBuilder().append(candidate)

The StringBuilder.append(long) function takes a long value, and converts it into string characters directly inside the StringBuilder.  This could potentially be faster.  It is even 3 characters shorter, while using a longer variable name, so it is certainly more concise!
Also, consider sb.append(start + &quot; &quot;);. This converts start to a string (again), adds &quot; &quot; to it (internally using another StringBuilder!), and then appends this brand new string to sb.  Instead, you could write:
sb.append(candidate).append(' ');

No intermediate strings needs to be constructed and discarded.
Avoid useless work
        long rev = ...;
        if(start &gt; 12 &amp;&amp; isPrime(rev) &amp;&amp; isPrime(start) &amp;&amp; start != rev) 
            ...

If start is not prime, is there any point computing rev?
    for(long candidate = start; candidate &lt;= end; candidate += 2) {
        if (isPrime(candidate)) {
            long rev = ...;
            if (isPrime(rev) &amp;&amp; candidate != rev)
                sb.append(candidate).append(' ');
        }
    }  

Better.  But what about that candidate != rev?  That looks like a very fast test, and isPrime(rev) is probably much, much slower.
    for(long candidate = start; candidate &lt;= end; candidate += 2) {
        if (isPrime(candidate)) {
            long rev = ...;
            if (candidate != rev &amp;&amp; isPrime(rev))
                sb.append(candidate).append(' ');
        }
    }

But wait!  How many times will candidate != rev be false?  Very rarely?  It may not gain anything, and the change may even slow the program down.  Always profile!
Performance at the cost of conciseness
Constructing a new StringBuilder inside the loop can be a costly operation.  You could construct one outside the loop, and use the .setLength(0) method to clear it, for the next iteration:
    StringBuilder temp = new StringBuilder();

    for(long candidate = start; candidate &lt;= end; candidate += 2) {
        if (isPrime(candidate)) {
            long rev = Long.parseLong(temp.append(candidate).reverse().toString());
            if (isPrime(rev) &amp;&amp; candidate != rev)
                sb.append(candidate).append(' ');
            temp.setLength(0);
        }
    }  

Major optimizations
Reduced search space
How many primes in the ranges 200-299, 400-699, and 800-899 will be reversible primes?  How about in the ranges 2000-2999, 4000-6999, 8000-8999?  How many in the ranges 20000-29999, 40000-69999, 80000-89999?  And so on?
Zero.  A reversible prime cannot start with a 2, 4, 5, 6, or 8, because when reversed, the number will be divisible by two or five, and thus won't be prime.
Implementing this observation in an efficient manner would certainly not make the code concise, but would dramatically speed it up.  Left to student.
Sieve of Eratosthenes
You are potentially doing many, many primality checks using trial division.  It may make more sense to use the Sieve of Eratosthenes to generate a BitSet of prime flags.  Then isPrime(candidate) becomes a simple \$O(1)\$ lookup: prime.get(candidate).
Alternately, if maintaining a BitSet of \$10^{\lceil \log_{10}{end} \rceil}\$ bits is too memory intensive, use the sieve to generate the prime numbers up to \$\sqrt{10^{\lceil \log_{10}{end} \rceil}}\$, and use trial division with just those prime numbers, to avoid pointless trial division by odd composite numbers (9, 15, 21, 25, 27, 33, ...).