over 4 years ago

The topoc today is about a samll change - finally, Java 8 added the Base64 encodder/decoder into the java.util package. Although Base64 increases the actual data transmission length, in the plain-text Internet protocols, Base64 is commonly used to encode the binary data as text (e.g, MIME email). Therefore, it is surprised that the Base64 support is added until Java 8. Before Java 8, it needs the third-party library, for example, Apache Commons Codec, to provide Base64 encoder and decoder.

In the past work, I was developing a system X, a job is to receive the user uploaded file A. For some reason, system X does not have the capacity for storing files, and the uploaded file is actually stored in another system Y that provides file content indexing. Another similar job is that when the user downloads a (machine generated) file B from the system X, the file is also stored in the system Y for auditing. Both the system X and the system Y are RESTful Web Service. The file content transmitting through HTTP between the system X and the system Y is Base64 encoded as text in a JSON object. And the enocder and decoder I used is Apache Commons Codec.

Since Java 8 includes the Base64 encoder and decoder, it's time to write some codes with the encoder and decoder. However, using the IOUils.copy(InputStream, OutputStream) method in Apache Commons IO to handle reading something from input and putting the data into output is my personal habit. If the Apache Commons IO is not used, the helper method in Code List 1 is usefaul - writing while loop to copy data from the input to output is not needed any more.

Code List 1 - Copy data from the input stream to the output stream
public static void copy(InputStream input, OutputStream output) throws IOException {
    byte[] buffer = new byte[4096];
    int length = 0;
    while((length = input.read(buffer)) != -1) {
        output.write(buffer, 0, length);
    }
}

With the helper method, the first program uses Base64 encoder to enocde the data from the InputStream and then write the encoded data into the OutputStream. In Code List 2, the encode(InputStream, OutputStream, Base64.Encoder) method accepts three parameters. The first is the data source and the second is the encoded data destination. These two are easy to understand, but how about the third parameter? Why do I have to specify the encoder? Are there different kinds of Base64 encoders? Yes! Base64 has variants in different protocols. Java 8 provides three kinds of Base64 encoders/decoders. The first kind is the basic (using Base64.getEncoder() and Base64.getDecoder() to get the basic encoder and decoder, respectively) which only uses 0-9, a-z, A-Z, +, /, and = alphabet to perform encoding, and the content is not line-separated. The second kind is the URL and Filename safe (using Base64.getUrlEncoder() and Base64.getUrlDecoder() to get the encoder and decoder). Since the symbols + and / have special usages in the URL, and / is illegal to use as filename in many filesystems, the encoder uses - (minus) to replace + and uses _ (underline) to replace /. The third kind is MIME (using Base64.getMimeEncoder() and Base64.getMimeDecoder() to get the encoder and decoder, respectively) which uses the same alphabet as the basic, but adding \r\n after every 76 characters for line-separation. Assume that the basic is commonly used, a method without the third parameter like Code List 2 can be provided for convenient.

Code List 2 - Encode the data from the input stream with Base64 encoder
public static void encode(InputStream input, OutputStream output, Base64.Encoder encoder) {
    try(OutputStream encodedOutput = encoder.wrap(output)) {
        copy(input, encodedOutput);
    } catch(IOException e) {
        e.printStackTrace();
    }
}

public static void encode(InputStream input, OutputStream output) {
    encode(input, output, Base64.getEncoder());
}

After encoding, a decoder can be used to decode the data. In Code List 3, the decode(InputStream, OutputStream, Base64.Decoder) method also accepts three parameters: the first is the encoded data source, the second is the decoded data destination, and the third is the decoder. The three kinds of encoders/decoders mentioned above can not be used hybridly. That is the content encoded with the basic encoder can only be decoded with the basic decoder. The same, a method without the third parameter can be provided for convenient, and in the the following examples, the convenient version with the basic encoder/decoder is not listed for space saving.

Code List 3 - Decode the data from the input stream with Base64 decoder
public static void decode(InputStream input, OutputStream output, Base64.Decoder decoder) {
    try(InputStream decodedInput = decoder.wrap(input)) {
        copy(decodedInput, output);
    } catch(IOException e) {
        e.printStackTrace();
    }
}

public static void decode(InputStream input, OutputStream output) {
    decode(input, output, Base64.getDecoder());
}

With the method that encodes or decodes the content reading from the InputStream and then writes the encoded or decoded data into the OutputStream, variant can be provided for different usages. For example, in Code List 4, the method accepts two files as the input and output. In the method, the files are wrapped by FileInputStream and FileOutputStream and can be fed as the parameters of the methods in Code List 2 and Code List 3 to perform encoding or decoding operation.

Code List 4 - Encode/Decode the content the source file to the target file
public static void encode(File source, File target, Base64.Encoder encoder) {
    try(InputStream input = new FileInputStream(source);
        OutputStream output = new FileOutputStream(target)) {
        encode(input, output, encoder);
    } catch(IOException e) {
        e.printStackTrace();
    }
}

public static void decode(File source, File target, Base64.Decoder decoder) {
    try(InputStream input = new FileInputStream(source);
        OutputStream output = new FileOutputStream(target)) {
        decode(input, output, decoder);
    } catch(IOException e) {
        e.printStackTrace();
    }
}

Well, the encoded or decoded content is not always written into a file. In the job experience mentioned above, the file content is encoded as a part of the JSON object. For that case, the method in Code List 5 is useful - encoding the file content as a string. The method uses ByteArrayOutputStream as the buffer to store the encoded or decoded bytes, and then call toString(String) to output the content as a string.

Code List 5 - Encode/Deocde the file content as a string
public static String encode(File source, Base64.Encoder encoder) {
    String result = null;
    try(InputStream input = new FileInputStream(source);
        ByteArrayOutputStream output = new ByteArrayOutputStream()) {
        encode(input, output, encoder);
        result = output.toString("UTF-8");
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}

public static String decode(File source, Base64.Decoder decoder) {
    String result = null;
    try(InputStream input = new FileInputStream(source);
        ByteArrayOutputStream output = new ByteArrayOutputStream()) {
        decode(input, output, decoder);
        result = output.toString("UTF-8");
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}

The same, the data source may not be a file - a string is also possible. For the case, the method in Code List 6 can be used. The method uses getBytes() to obtain the string content as a byte array and wraps the byte array with ByteArrayInputStream as the input stream, and then feeds the input stream and the output stream for encoding or decoding operation.

Code List 6 - Encode/Decode the string
public static String encode(String source, Base64.Encoder encoder) {
        String result = null;
        try(InputStream input = new ByteArrayInputStream(source.getBytes());
            ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            encode(input, output, encoder);
            result = output.toString("UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

public static String decode(String source, Base64.Decoder decoder) {
    String result = null;
    try(InputStream input = new ByteArrayInputStream(source.getBytes());
        ByteArrayOutputStream output = new ByteArrayOutputStream()) {
        decode(input, output, decoder);
        result = output.toString("UTF-8");
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}

Java 8 includes the Base64 encoder and decoder to provide convenience. However, the third-party libraries like Apache Commons Codec usually provide many kinds of encoders and decoders more than Base64 - that is much more convenient. But if your application only needs Base64 encoder and decoder, in the Java 8 environment, the third-party library for Base64 encoder and decoder is not required. Well, only in the Java 8 environment, therefore, using the built-in Base64 encoder and decoder becomes a design trade-off.

Puzzle: try to decode the file (tip: the file is a PNG picture.)

← Java 8 初探 - Base64 Java 8 初探 - Parallel Array Sort →