46

How does one generate a random string sequence with Apex? I need to generate a code when a particular object is created in our Salesforce environment. I'd like the code to contain numbers from 0-9 and letters from A-Z.

Searching their rather awful developer forums I found:

Blob blobKey = crypto.generateAesKey(128);
String key = EncodingUtil.convertToHex(blobKey);
return key.substring(0,len);

However this isn't going to cover a very large range of letters.

In the C# world something like the following would do the trick:

var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var random = new Random();
var result = new string(
    Enumerable.Repeat(chars, numberOfCharacters)
              .Select(s => s[random.Next(s.Length)])
              .ToArray()
);

Is there an easy way to do something similar in Apex that I'm just not familiar with?

6 Answers 6

56

Here is a static method that accepts a desired string length as an argument. It takes a full string of ASCII numbers and letters and loops through the index of your desired string length, randomly choosing an index in the full character string.

public static String generateRandomString(Integer len) {
    final String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
    String randStr = '';
    while (randStr.length() < len) {
       Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length());
       randStr += chars.substring(idx, idx+1);
    }
    return randStr; 
}
3
  • I've added a brief explanation.
    – greenstork
    Jul 11, 2014 at 15:57
  • Nice, question, how would you test this code? Maybe it would not be required. Jan 13, 2021 at 7:42
  • You could test this by testing correct size, correct character types, and sufficient randomness (from: salesforce.stackexchange.com/a/256526/44105)
    – moth
    Sep 29, 2021 at 4:16
34

You can create random strings by stringing together Integer values. Here's how I would perform such a task:

public static String randomStringAll(Integer count) {
    Integer[] chars = new Integer[0],
        offsets = new Integer[] { 48, 65, 97 },
            mods = new Integer[] { 10, 26, 26 };

    while(count > chars.size()) {
        Integer cat = Math.abs(Math.mod(Crypto.getRandomInteger(),3));
        chars.add(Math.mod(Math.abs(Crypto.getRandomInteger()), mods[cat])+offsets[cat]);
    }
    return String.fromCharArray(chars);
}

You can extend this to include any Unicode characters, but, of course, this is plain ASCII ("Latin") characters, a-z, A-Z, and 0-9.

Edit: Forgot to use absolute value. Fixed.

Edit: In response to the question/comment, I tried a simple benchmark (I'll come back and perform heavier analysis later).

I created two tests. The first test calls the function to make a string 100,000 large. All times are in MS.

    generateRandomString     randomStringAll
1   5369                     5165
2   4856                     4151
3   5754                     4447
4   4821                     4359
5   4985                     4171
Avg 5157                     4458.6

Then, I created a test where we requested 10,000 strings 10 characters long. All times are in MS.

1   3437                     4813
2   3102                     5030
3   3478                     5268
4   3180                     5270
5   3437                     4953
Avg 3326.8                   5066.8

Observations:

Repeated string concatenation seems to slow the system down at larger sizes. Crypto.getRandomInteger/getRandomLong is really expensive, so at smaller sizes, generateRandom string is superior, while at larger sizes, the lack of memory thrashing makes randomStringAll faster.

Based on these observations, I made further adjustments, and finally came up with:

public static String randomStringAllEnhanced2(Integer count) {
    Integer[] chars = new Integer[count],
        offsets = new Integer[] { 48, 65, 97 },
            mods = new Integer[] { 10, 26, 26 };

    while(count > 0) {
        Integer rnd = Math.abs(Crypto.getRandomInteger()), cat = Math.mod(rnd, 3), seed = Math.mod(rnd, mods[cat]);
        chars[(count--)-1] = seed+offsets[cat];
    }
    return String.fromCharArray(chars);
}

I ran the same tests, and came up with the following, first for 100k strings:

1       5369    2940
2       4856    3258
3       5754    2935
4       4821    2942
5       4985    3093
Avg     5157    3033.6

This method, taking advantage of static memory, and reducing the number of Crypto calls, made this function blazing fast (comparatively) for very large strings.

Then, I ran the second test, and came up with the following numbers:

1       3437    3218
2       3102    3425
3       3478    3465
4       3180    3696
5       3437    3931
Avg     3326.8  3547

Here, the winner appears to still be generateRandomString, but due to the few number of samples, a deviation of simply 200 ms is probably statistically insignificant (I'd say it takes at least 500 ms to be statistically interesting).

I'll probably run an extended batch of 1000 or more each through Visualforce or something to see the long-term gains/loss, but I think the point here is that generateRandomString is probably better if you need alphanumeric characters. If you need just a-z or A-Z, you can use these:

public static String randomStringUCAZ(Integer count) {
    Integer[] chars = new Integer[0];
    while(count > chars.size()) {
        chars.add(Math.mod(Crypto.getRandomInteger(),26)+65);
    }
    return String.fromCharArray(chars);
}
public static String randomStringLCAZ(Integer count) {
    Integer[] chars = new Integer[0];
    while(count > chars.size()) {
        chars.add(Math.mod(Crypto.getRandomInteger(),26)+97);
    }
    return String.fromCharArray(chars);
}

100k Strings were impressive:

1       1881
2       1867
3       1942
4       1774
5       1790
Avg     1850.8

10k 10 character strings were blazing:

1       2092
2       1991
3       2245
4       1971
5       1976
Avg     2055

After this experiment, I realized that we could try to combine what appeared to be faster features, and came up with:

public static String generateRandomString2(Integer len) {
    String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
    String[] result = new String[len];
    Integer idx = 0;

    while(idx < len) {
        Integer chr = Math.mod(Math.abs(Crypto.getRandomInteger()), 62);
        result[idx++] = chars.substring(chr, chr+1);
    }
    return String.join(result,'');
}

This method excels at longer strings than shorter, but shows little statistical difference between generateRandomString (but on average, faster, for a small sample).

100k Strings:

1       2842
2       2772
3       2707
4       2511
5       2641
Avg     2694.6

10k 10 character strings:

1       3034
2       3415
3       3367
4       3084
5       2798
Avg     3139.6

Finally, I tried creating a function that had a better "average" performance across big and small strings (all prior functions seemed to have some specific use), and the result was:

static Integer[] charset;

static {
    charset = new Integer[0];
    for(Integer i = 48; i < 58; i++) charset.add(i);
    for(Integer i = 65; i < 91; i++) charset.add(i);
    for(Integer i = 97; i < 123; i++) charset.add(i);
}

public static String genRndStrFast(Integer len) {
    Integer[] chars = new Integer[len];
    Integer key, size = charset.size();

    for(Integer idx = 0; idx < len; idx++) {
        chars[idx] = charset[Math.mod(Math.abs(Crypto.getRandomInteger()), size)];
    }

    return String.fromCharArray(chars);
}

Despite the extra initialization time (static variable), "caching" this data resulted in smoother performance:

100k strings:

1       2104
2       2077
3       2038
4       2276
5       2266
Avg     2152.2

10k 10 character strings:

1       2489
2       2056
3       2594
4       2402
5       2333
Avg     2374.8

Overall, there's little benefit to choosing one function over the other if you're calling it just once, but there is a benefit in deciding which algorithm to use depending on how long of a string you need or when you need to generate many strings. This answer originally was simply meant to be an alternative function, but actually yielded tons of useful information.

6
  • 6
    Do you view that your answer has an advantage over the other answer provided? I'm asking for my own sake and for the sake of future travelers that stumble upon this question. Jul 10, 2014 at 18:26
  • 4
    @Ek0nomik See what you made me do? At least now, we know which has better performance...
    – sfdcfox
    Jul 10, 2014 at 20:16
  • You win the internet today sfcdfox, great research.
    – greenstork
    Jul 10, 2014 at 21:26
  • @sfdcfox As usual, the only champ here is you... Mind blowing explanation!! May 30, 2015 at 13:44
  • Ran some further testing and optimization on your final result and found that you can get about a 20-25% performance increase by changing how you calculate your random index to: (Integer)Math.floor( Math.random() * size ) Oct 25, 2015 at 21:25
10

From your original, proposal, you can expand the AES key and use base64encode to get a wider range of characters. Doing:

Blob blobKey = crypto.generateAesKey(192);
String key = EncodingUtil.base64encode(blobKey);
return key.substring(0,len);

Gets you a string of the same length but with 64 different characters instead of 16. It also runs much faster than manually using loops and indexes to create your string (for 1000 x 32 char strings, I timed 158 ms, vs 5535 ms for manually).

(Btw, here's the template I use for comparing algorithm performances on Salesforce. http://pastebin.com/XDAtu4ip)

6

Based on what @pablo-venturino said - in one line:

System.debug(EncodingUtil.convertToHex(Crypto.generateAesKey(128)).substring(0, 32));

output examples:

  • fb94795f3b883ea0488035931dd13e8f
  • 8ff952acaa37dc8e9735202ae1a40e3e
  • f24813d9f08a4096b61c5e986f4087ab
1

I wrote a class some time ago that generates all sort random entities. It's handy to keep it in your org.

http://www.codebycody.com/2012/02/sfdc-randomizer.html

just copy the code from the class to your org and call the getRandomString method

1
0

Improvised @pablo-venturino to get only AlphaNumeric random strings instead of special character that comes with Base64.

 String key = EncodingUtil.base64Encode(Crypto.generateAesKey(192));
 key = key.replaceAll('[+/=]', '');
 return key.substring(0,len);

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .