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
| Mode | Description | Use when |
|---|---|---|
| Write lock | Exclusive lock — blocks all readers and writers | Mutating shared state |
| Read lock | Shared lock — multiple readers allowed | Reading shared state |
| Optimistic read | No lock taken — just read, then validate | Read-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
| Property | ReentrantReadWriteLock | StampedLock |
|---|---|---|
| Optimistic reads | No | Yes |
| Reentrant | Yes | No |
| Condition support | Yes | No |
| Performance (read-heavy) | Moderate | High |
Use StampedLock when:
- Reads vastly outnumber writes
- Reentrancy is not needed
- Maximum throughput matters
Use ReentrantReadWriteLock when:
- You need reentrant locking or
Conditionobjects - 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 case | Recommendation |
|---|---|
| Pure counter under high concurrency | LongAdder |
| CAS-based update that needs current value | AtomicLong |
| Single-threaded | long 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
| API | Use case |
|---|---|
Base64 | Encode/decode binary data; Basic Auth headers; embedding binary in JSON |
StampedLock | Read-heavy concurrent data structures needing maximum throughput |
LongAdder | High-throughput concurrent counters (hits, requests, events) |
LongAccumulator | High-throughput concurrent reduce with any commutative op |
Nashorn | Scripting in Java 8 (deprecated in Java 11; use GraalVM for new projects) |
Arrays.parallelSort | Parallel sort of large arrays |
StringJoiner | Building delimited strings; used by Collectors.joining |
Next Step
JVM Improvements: Metaspace, PermGen Removal, and Performance →
Part of the DevOps Monk Java tutorial series: Java 8 → Java 11 → Java 17 → Java 21