Part 14 of 16

New APIs: Base64, StampedLock, Nashorn JavaScript Engine

Base64

Before Java 8, Base64 encoding required a third-party library (Apache Commons Codec, Guava) or the internal sun.misc.BASE64Encoder class. Java 8 added java.util.Base64 to the standard library.

Three Encoders

// Standard Base64 (RFC 4648)
Base64.Encoder encoder = Base64.getEncoder();
Base64.Decoder decoder = Base64.getDecoder();

// URL-safe Base64 (replaces + with -, / with _) — safe in URLs and filenames
Base64.Encoder urlEncoder = Base64.getUrlEncoder();
Base64.Decoder urlDecoder = Base64.getUrlDecoder();

// MIME Base64 (line-wrapped at 76 chars with CRLF — for email)
Base64.Encoder mimeEncoder = Base64.getMimeEncoder();
Base64.Decoder mimeDecoder = Base64.getMimeDecoder();

Encoding and Decoding

String original = "Hello, World! This is a test: 1+2=3";

// Encode to Base64 string
byte[] encoded = Base64.getEncoder().encode(original.getBytes(StandardCharsets.UTF_8));
String encodedStr = Base64.getEncoder().encodeToString(original.getBytes(StandardCharsets.UTF_8));
// SGVsbG8sIFdvcmxkISBUaGlzIGlzIGEgdGVzdDogMSsyPTM=

// Decode
byte[] decoded = Base64.getDecoder().decode(encodedStr);
String decodedStr = new String(decoded, StandardCharsets.UTF_8);
// "Hello, World! This is a test: 1+2=3"

Without Padding

Base64.Encoder nopadEncoder = Base64.getEncoder().withoutPadding();
String nopad = nopadEncoder.encodeToString("test".getBytes());
// dGVzdA   (no trailing ==)

Streaming Encoding

// Wrap an OutputStream to encode on the fly
try (OutputStream out = Base64.getEncoder().wrap(new FileOutputStream("encoded.txt"))) {
    out.write("Large binary data".getBytes());
}

Practical: Basic Authentication Header

String credentials = "username:password";
String authHeader = "Basic " + Base64.getEncoder()
    .encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
// Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Practical: Encode/Decode Binary Data

// Encode image bytes for embedding in JSON
byte[] imageBytes = Files.readAllBytes(Paths.get("photo.jpg"));
String base64Image = Base64.getEncoder().encodeToString(imageBytes);

// Decode back
byte[] decoded = Base64.getDecoder().decode(base64Image);
Files.write(Paths.get("photo_copy.jpg"), decoded);

StampedLock

StampedLock (Java 8, java.util.concurrent.locks) provides three locking modes and is significantly faster than ReadWriteLock in read-heavy scenarios.

Three Modes

ModeDescriptionUse when
Write lockExclusive lock — blocks all readers and writersMutating shared state
Read lockShared lock — multiple readers allowedReading shared state
Optimistic readNo lock taken — just read, then validateRead-heavy, rarely written

Optimistic Read (the key feature)

Optimistic reads do not block writers. After reading, you validate the stamp — if a write occurred, you fall back to a proper read lock:

public class Point {
    private double x, y;
    private final StampedLock lock = new StampedLock();

    public double distanceFromOrigin() {
        // Try optimistic read first
        long stamp = lock.tryOptimisticRead();
        double curX = x, curY = y;

        if (!lock.validate(stamp)) {
            // A write happened — fall back to read lock
            stamp = lock.readLock();
            try {
                curX = x;
                curY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return Math.sqrt(curX * curX + curY * curY);
    }

    public void move(double deltaX, double deltaY) {
        long stamp = lock.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

Lock Conversion

StampedLock allows upgrading a read lock to a write lock without releasing it:

long stamp = lock.readLock();
try {
    // Inspect state...
    if (needsUpdate) {
        long writeStamp = lock.tryConvertToWriteLock(stamp);
        if (writeStamp != 0L) {
            stamp = writeStamp;
            // now holds write lock — mutate state
        } else {
            lock.unlockRead(stamp);
            stamp = lock.writeLock();
        }
    }
} finally {
    lock.unlock(stamp);
}

StampedLock vs ReadWriteLock

PropertyReentrantReadWriteLockStampedLock
Optimistic readsNoYes
ReentrantYesNo
Condition supportYesNo
Performance (read-heavy)ModerateHigh

Use StampedLock when:

  • Reads vastly outnumber writes
  • Reentrancy is not needed
  • Maximum throughput matters

Use ReentrantReadWriteLock when:

  • You need reentrant locking or Condition objects
  • The locking logic is complex

LongAdder and LongAccumulator

LongAdder

LongAdder (and DoubleAdder) is a high-throughput counter designed for concurrent updates. Under contention, AtomicLong makes every thread compete for a single cell; LongAdder maintains multiple cells and only sums them on sum().

LongAdder counter = new LongAdder();

// Concurrent updates from many threads
counter.increment();
counter.add(5L);
counter.decrement();

// Read the sum (approximately — not linearisable)
long total = counter.sum();
long totalAndReset = counter.sumThenReset();

When to use LongAdder vs AtomicLong:

Use caseRecommendation
Pure counter under high concurrencyLongAdder
CAS-based update that needs current valueAtomicLong
Single-threadedlong primitive

LongAccumulator

Generalises LongAdder to any commutative and associative operation:

// Track maximum observed value
LongAccumulator maxVal = new LongAccumulator(Long::max, Long.MIN_VALUE);
maxVal.accumulate(42L);
maxVal.accumulate(100L);
maxVal.accumulate(7L);
System.out.println(maxVal.get()); // 100

Nashorn JavaScript Engine

Java 8 shipped Nashorn (javax.script + jdk.nashorn.api), a high-performance JavaScript engine built on invokedynamic.

Note: Nashorn was deprecated in Java 11 and removed in Java 15. If you are starting a new Java 8 project, prefer GraalVM Polyglot for scripting needs. This section covers Nashorn for completeness in Java 8.

Basic Script Execution

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

// Execute JavaScript
engine.eval("print('Hello from JavaScript!')");

// Evaluate an expression
Object result = engine.eval("2 + 2");
System.out.println(result); // 4

Passing Data Between Java and JavaScript

// Set a Java variable accessible in JS
engine.put("name", "Alice");
engine.eval("print('Hello, ' + name)"); // Hello, Alice

// Get a value from JS back in Java
engine.eval("var greeting = 'Salut'");
String greeting = (String) engine.get("greeting");

// Call a JS function from Java
engine.eval("function square(x) { return x * x; }");
Invocable invocable = (Invocable) engine;
Object squared = invocable.invokeFunction("square", 9);
System.out.println(squared); // 81.0

Using Java Classes from JavaScript

engine.eval("""
    var ArrayList = Java.type('java.util.ArrayList');
    var list = new ArrayList();
    list.add('one');
    list.add('two');
    print(list.size());  // 2
""");

Loading a Script File

engine.eval(new FileReader("script.js"));

Why Nashorn Was Deprecated

  • ECMAScript compliance was incomplete and hard to keep up with spec evolution
  • GraalVM’s Graal.js provides full ES2020+ support and better performance
  • Maintaining a separate JS engine in the JDK was not worth the cost

For Java 8 projects that need scripting, Nashorn is available but be aware of its future removal. Plan for migration to GraalVM Polyglot if scripting is a core requirement.


Other Noteworthy Java 8 API Additions

Arrays.parallelSort

int[] arr = {5, 3, 1, 4, 2};
Arrays.parallelSort(arr); // uses ForkJoin for large arrays

Arrays.parallelPrefix

Computes a running accumulation in parallel:

int[] arr = {1, 2, 3, 4, 5};
Arrays.parallelPrefix(arr, Integer::sum);
// arr is now [1, 3, 6, 10, 15] — cumulative sum

Objects utility methods

// Null-safe comparison
int cmp = Objects.compare(a, b, Comparator.naturalOrder());

// isNull / nonNull predicates (useful in streams)
names.stream().filter(Objects::nonNull).collect(Collectors.toList());

// requireNonNullElse (Java 9+, but preview of the pattern)
String value = Objects.requireNonNull(param, "param must not be null");

StringJoiner

StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("Alice");
sj.add("Bob");
sj.add("Charlie");
System.out.println(sj); // [Alice, Bob, Charlie]

// Used internally by Collectors.joining

Summary

APIUse case
Base64Encode/decode binary data; Basic Auth headers; embedding binary in JSON
StampedLockRead-heavy concurrent data structures needing maximum throughput
LongAdderHigh-throughput concurrent counters (hits, requests, events)
LongAccumulatorHigh-throughput concurrent reduce with any commutative op
NashornScripting in Java 8 (deprecated in Java 11; use GraalVM for new projects)
Arrays.parallelSortParallel sort of large arrays
StringJoinerBuilding delimited strings; used by Collectors.joining

Next Step

JVM Improvements: Metaspace, PermGen Removal, and Performance →

Part of the DevOps Monk Java tutorial series: Java 8Java 11Java 17Java 21