118

UUID libraries generate 32-character UUIDs.

I want to generate 8-character only UUIDs, is it possible?

Elouan Keryell-Even
  • 990
  • 1
  • 14
  • 36
M.J.
  • 16,266
  • 28
  • 75
  • 97
  • Sure. But it's propably not as straightforward and shorter equals less likely to be actually unique. So why? –  Nov 24 '10 at 13:52
  • @delnan, to be used in embedded environment? – Allen Zhang Mar 13 '14 at 23:01
  • 1
    If the resulting string can be stored in UTF-8 you potentially have 4 bytes per character. If you can use that whole range you would only need 4 UTF-8 characters to represent the same information. – EECOLOR Apr 13 '18 at 09:57
  • why not use SQL uuid and take only first 8 characters? The other are the same for every ID. – S. W. G. Apr 14 '22 at 16:05

9 Answers9

92

It is not possible since a UUID is a 16-byte number per definition. But of course, you can generate 8-character long unique strings (see the other answers).

Also be careful with generating longer UUIDs and substring-ing them, since some parts of the ID may contain fixed bytes (e.g. this is the case with MAC, DCE and MD5 UUIDs).

janisz
  • 6,292
  • 4
  • 37
  • 70
Cephalopod
  • 14,632
  • 7
  • 51
  • 70
91

You can try RandomStringUtils class from apache.commons:

import org.apache.commons.lang3.RandomStringUtils;

final int SHORT_ID_LENGTH = 8;

// all possible unicode characters
String shortId = RandomStringUtils.random(SHORT_ID_LENGTH);

Please keep in mind, that it will contain all possible characters which is neither URL nor human friendly.

So check out other methods too:

// HEX: 0-9, a-f. For example: 6587fddb, c0f182c1
shortId = RandomStringUtils.random(8, "0123456789abcdef"); 

// a-z, A-Z. For example: eRkgbzeF, MFcWSksx
shortId = RandomStringUtils.randomAlphabetic(8); 

// 0-9. For example: 76091014, 03771122
shortId = RandomStringUtils.randomNumeric(8); 

// a-z, A-Z, 0-9. For example: WRMcpIk7, s57JwCVA
shortId = RandomStringUtils.randomAlphanumeric(8); 

As others said probability of id collision with smaller id can be significant. Check out how birthday problem applies to your case. You can find nice explanation how to calculate approximation in this answer.

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
Anton
  • 10,890
  • 8
  • 45
  • 54
  • 4
    Since `org.apache.commons.lang3.RandomStringUtils` is deprecated, you'd be better using `org.apache.commons.text.RandomStringGenerator` in https://commons.apache.org/proper/commons-text/ – Bruno Medeiros Sep 24 '17 at 03:45
  • Added a new answer for `RandomStringGenerator`, as it is quite different code. – Bruno Medeiros Dec 10 '17 at 16:17
  • 4
    Just an FYI for the future viewers, Randomness doesn't guarantee uniqueness. Random generators guarantee randomness; and can produce a valid set of random numbers with repeat values. – Vishnu Prasad V Feb 01 '18 at 14:37
  • `RandomStringUtils` is NOT deprecated. It's intended for simple use. Can you provide a source of the information that `RandomStringUtils` is deprecated? I can provide the documentation of the latest version of `RandomStringUtils` as a proof that it is not deprecated: http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/RandomStringUtils.html – krm Jul 20 '19 at 23:47
  • Only by checking a map or hashset with already used uuids, probability for collision is huge. – Anton Mar 19 '20 at 16:23
  • Okay answer, but awesome value in that last link. Thank you! – KlaymenDK Oct 08 '20 at 07:12
26

First: Even the unique IDs generated by java UUID.randomUUID or .net GUID are not 100% unique. Especialy UUID.randomUUID is "only" a 128 bit (secure) random value. So if you reduce it to 64 bit, 32 bit, 16 bit (or even 1 bit) then it becomes simply less unique.

So it is at least a risk based decisions, how long your uuid must be.

Second: I assume that when you talk about "only 8 characters" you mean a String of 8 normal printable characters.

If you want a unique string with length 8 printable characters you could use a base64 encoding. This means 6bit per char, so you get 48bit in total (possible not very unique - but maybe it is ok for you application)

So the way is simple: create a 6 byte random array

 SecureRandom rand;
 // ...
 byte[] randomBytes = new byte[16];
 rand.nextBytes(randomBytes);

And then transform it to a Base64 String, for example by org.apache.commons.codec.binary.Base64

BTW: it depends on your application if there is a better way to create "uuid" then by random. (If you create a the UUIDs only once per second, then it is a good idea to add a time stamp) (By the way: if you combine (xor) two random values, the result is always at least as random as the most random of the both).

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Ralph
  • 118,862
  • 56
  • 287
  • 383
  • 3
    I know this is an old answer, but the point stands - java's UUID and .net's GUID are 100% unique. You, and the rest of the universe, will quite simply never have a uuid collision. Even if you encountered over 100 trillion UUIDs, the likelihood of a collision is still less than 1 in a billion. https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions – corsiKa Dec 21 '21 at 04:12
  • "One in a billion" is not never though. – Parzh from Ukraine May 02 '22 at 15:25
9

As @Cephalopod stated it isn't possible but you can shorten a UUID to 22 characters

public static String encodeUUIDBase64(UUID uuid) {
        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());
        return StringUtils.trimTrailingCharacter(BaseEncoding.base64Url().encode(bb.array()), '=');
}
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
bstick12
  • 1,699
  • 12
  • 18
4

This is a similar way I'm using here to generate an unique error code, based on Anton Purin answer, but relying on the more appropriate org.apache.commons.text.RandomStringGenerator instead of the (once, not anymore) deprecated org.apache.commons.lang3.RandomStringUtils:

@Singleton
@Component
public class ErrorCodeGenerator implements Supplier<String> {

    private RandomStringGenerator errorCodeGenerator;

    public ErrorCodeGenerator() {
        errorCodeGenerator = new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                .filteredBy(t -> t >= '0' && t <= '9', t -> t >= 'A' && t <= 'Z', t -> t >= 'a' && t <= 'z')
                .build();
    }

    @Override
    public String get() {
        return errorCodeGenerator.generate(8);
    }

}

All advices about collision still apply, please be aware of them.

Bruno Medeiros
  • 2,251
  • 21
  • 34
  • `RandomStringUtils` is NOT deprecated. It's intended for simple use. Can you provide a source of the information that `RandomStringUtils` is deprecated? I can provide the documentation of the latest version of `RandomStringUtils` as a proof that it is not deprecated: http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/RandomStringUtils.html – krm Jul 20 '19 at 23:45
  • Well, if you dig a little bit further, you'll see that, as of the time of writing of this answer, the latest release had indeed deprecated this class: https://github.com/apache/commons-lang/commits/master/src/main/java/org/apache/commons/lang3/RandomStringUtils.java Probably some feedback (https://user.commons.apache.narkive.com/GVBG2Ar0/commons-lang3-too-early-to-deprecate-randomstringutils-in-favor-of-randomstringgenerator) made it back. You shouldn't be using anything in `commons.lang` that is not strictly related to the language itself anyway, `commons.text` was created with a purpose. – Bruno Medeiros Jul 21 '19 at 04:14
  • Thank you for the explanation BrunoJCM. At the current moment `RandomStringUtils` is not deprecated and according to the references provided by you there is a good reason to keep it not deprecated, because it is much simpler to use than `RandomStringGenerator` for simple use cases. Maybe you can update your answer? If/when `RandomStringUtils` or its functionality for simple use cases will be moved to `commons.text`, then you can update your answer again, but currently it is misleading. – krm Jul 24 '19 at 14:20
  • Added a note, but again, it's clear that Apache Commons project is moving text utils from `commons.lang` to `commons.text`, there is no reason for anyone to be using the former rather than the latter other than being using it already somewhere else. Simplicity here is rather subjective, I find my answer still very simple, and I'd never change it for something that would require Commons Lang to be imported. – Bruno Medeiros Jul 25 '19 at 15:08
3

Not a UUID, but this works for me:

UUID.randomUUID().toString().replace("-","").substring(0,8)
  • 8
    this might be a problematic solution because some part of uuid might be common or repeat frequently – Supreet Singh Feb 02 '22 at 20:19
  • To make the previous comment a bit more concrete: UUID.randomUUID() creates a version 4 UUID: 'As in other UUIDs, 4 bits are used to indicate version 4, and 2 or 3 bits to indicate the variant (102 or 1102 for variants 1 and 2 respectively).' (wikipedia UUID). This leaves 121 or 122 bits for randomly generated part. – wearego Jul 11 '23 at 08:54
2

How about this one? Actually, this code returns 13 characters max, but it shorter than UUID.

import java.nio.ByteBuffer;
import java.util.UUID;

/**
 * Generate short UUID (13 characters)
 * 
 * @return short UUID
 */
public static String shortUUID() {
  UUID uuid = UUID.randomUUID();
  long l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong();
  return Long.toString(l, Character.MAX_RADIX);
}
sanghoon2
  • 77
  • 3
  • 6
    You know that `getLong()` is only reading the first 8 bytes of the buffer. The UUID will have at least 36 bytes. Am I missing something because to me this would never work. – Edwin Dalorzo Oct 07 '14 at 01:40
  • 2
    The first 8 bytes is the most significant bits of UUID. according to [this answer](http://stackoverflow.com/a/325457/851344) the less significant bits is more random. So `Long.toString(uuid.getLessSignificantBits(), Character.MAX_RADIX)` is better. – DouO Dec 10 '14 at 09:00
  • I tried this solution but it fails on adding this shortUUID in a set with 2M entries. so i will not recommend this one. – justice Dec 21 '22 at 21:58
0

Actually I want timestamp based shorter unique identifier, hence tried the below program.

It is guessable with nanosecond + ( endians.length * endians.length ) combinations.

public class TimStampShorterUUID {

    private static final Character [] endians = 
           {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 
            'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 
            'u', 'v', 'w', 'x', 'y', 'z', 
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 
            'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 
            'U', 'V', 'W', 'X', 'Y', 'Z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
            };

   private static ThreadLocal<Character> threadLocal =  new ThreadLocal<Character>();

   private static AtomicLong iterator = new AtomicLong(-1);


    public static String generateShorterTxnId() {
        // Keep this as secure random when we want more secure, in distributed systems
        int firstLetter = ThreadLocalRandom.current().nextInt(0, (endians.length));

        //Sometimes your randomness and timestamp will be same value,
        //when multiple threads are trying at the same nano second
        //time hence to differentiate it, utilize the threads requesting
        //for this value, the possible unique thread numbers == endians.length
        Character secondLetter = threadLocal.get();
        if (secondLetter == null) {
            synchronized (threadLocal) {
                if (secondLetter == null) {
                    threadLocal.set(endians[(int) (iterator.incrementAndGet() % endians.length)]);
                }
            }
            secondLetter = threadLocal.get();
        }
        return "" + endians[firstLetter] + secondLetter + System.nanoTime();
    }


    public static void main(String[] args) {

        Map<String, String> uniqueKeysTestMap = new ConcurrentHashMap<>();

        Thread t1 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t2 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t3 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t4 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t5 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }
        };

        Thread t6 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }   
        };

        Thread t7 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }
        };

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
        t7.start();
    }
}

UPDATE: This code will work on single JVM, but we should think on distributed JVM, hence i am thinking two solutions one with DB and another one without DB.

with DB

Company name (shortname 3 chars) ---- Random_Number ---- Key specific redis COUNTER
(3 char) ------------------------------------------------ (2 char) ---------------- (11 char)

without DB

IPADDRESS ---- THREAD_NUMBER ---- INCR_NUMBER ---- epoch milliseconds
(5 chars) ----------------- (2char) ----------------------- (2 char) ----------------- (6 char)

will update you once coding is done.

Kanagavelu Sugumar
  • 18,766
  • 20
  • 94
  • 101
-14

I do not think that it is possible but you have a good workaround.

  1. cut the end of your UUID using substring()
  2. use code new Random(System.currentTimeMillis()).nextInt(99999999); this will generate random ID up to 8 characters long.
  3. generate alphanumeric id:

    char[] chars = "abcdefghijklmnopqrstuvwxyzABSDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray();
    Random r = new Random(System.currentTimeMillis());
    char[] id = new char[8];
    for (int i = 0;  i < 8;  i++) {
        id[i] = chars[r.nextInt(chars.length)];
    }
    return new String(id);
    
AlexR
  • 114,158
  • 16
  • 130
  • 208