← All Articles
Encoding

Base64 Encoding in Real Projects — When, Why and How

Base64 appears in more places in a typical web application than most developers realise — HTTP Basic Auth headers, JWT tokens, data URIs in CSS, file upload APIs, email attachments, and TLS certificates. Understanding when and why Base64 is used (and when it should not be) will make you more effective at debugging API issues, reading network traffic, and designing data transfer mechanisms.

What Base64 actually does

Base64 is a way to represent arbitrary binary data using a 64-character alphabet of printable ASCII: A–Z, a–z, 0–9, +, and /. The name comes from that alphabet. A 65th character, =, is used as padding when the input length is not a multiple of 3, so the encoded output actually draws on 65 characters in total. Every 3 bytes of input binary data are encoded as 4 Base64 characters — a 4:3 expansion ratio, meaning output is approximately 33% larger than the input.

The reason Base64 exists is that many data transmission channels — email (SMTP), HTTP headers, URLs, XML, JSON — were designed to handle text, not arbitrary binary bytes. Before Base64, attaching a binary file to an email or embedding an image in an HTML document required protocol-level workarounds. Base64 provides a reliable, universally supported way to represent binary data as printable text.

HTTP Basic Authentication

HTTP Basic Auth encodes credentials as Base64 and sends them in the Authorization header. The format is:

// Credentials
username: admin
password: s3cr3tP@ssw0rd

// Encoded
Base64("admin:s3cr3tP@ssw0rd") = "YWRtaW46czNjcjN0UEBzc3cwcmQ="

// Header
Authorization: Basic YWRtaW46czNjcjN0UEBzc3cwcmQ=

Critical security note: Base64 is not encryption. Anyone who intercepts the header can decode the credentials immediately. HTTP Basic Auth must only ever be used over HTTPS (TLS), never over plain HTTP. Even over HTTPS, prefer token-based authentication (OAuth 2.0, API keys) for production APIs.

JWT tokens

JSON Web Tokens use Base64URL encoding — a variant that replaces + with - and / with _. The padding is a separate matter: RFC 4648 base64url keeps the trailing = characters, and it is the JOSE specification (RFC 7515) that requires them to be stripped. That is why no JWT segment ever ends in =. A signed JWT (JWS) has three sections — header, payload, signature — each separately Base64URL-encoded, and you can decode the header and payload of one without knowing the signing key; they are not encrypted, just encoded. An encrypted JWT (JWE) is a different shape: five sections, with a payload you cannot read this way.

// Decode JWT payload in JavaScript
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
    + ".eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBcmp1biIsImV4cCI6MTczNTY4OTYwMH0"
    + ".-AbGmHt5yVNpPaden8NuPS_z18k4Y09XhtjQt_Ew_pA";
const [, payload] = token.split('.');
const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
// → { sub: 'user_123', name: 'Arjun', exp: 1735689600 }

Data URIs — embedding images in CSS and HTML

Instead of referencing an external image file, you can embed the image data directly in your CSS or HTML as a Base64 data URI. This eliminates an HTTP request but increases file size:

/* In CSS */
.logo {
    background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...");
}

/* In HTML */
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." alt="logo" />

Data URIs are best for small, frequently used assets — icons and logos, where the cost of an extra HTTP request outweighs the extra bytes. As the asset grows, that trade reverses: the 33% size increase and the inability to cache the image separately from the document make an external file preferable.

File uploads and APIs

REST APIs that need to accept binary file uploads in a JSON body often use Base64 encoding. Instead of multipart/form-data, the file is encoded and embedded as a string field:

// Request body
{
  "filename": "invoice.pdf",
  "content_type": "application/pdf",
  "data": "JVBERi0xLjQKJeLjz9MKNiAwIG9iagox..."  // Base64-encoded PDF
}

// Java — encoding a file for upload
byte[] fileBytes = Files.readAllBytes(Path.of("invoice.pdf"));
String base64Data = Base64.getEncoder().encodeToString(fileBytes);

// Java — decoding received Base64 back to bytes
byte[] decoded = Base64.getDecoder().decode(base64Data);
Files.write(Path.of("output.pdf"), decoded);

This pattern is common in document processing APIs, email APIs (SendGrid, Mailgun), and cloud storage APIs. The tradeoff is larger request bodies — for very large files, multipart uploads or direct-to-storage presigned URLs are more efficient.

Email attachments (MIME)

Email attachments are Base64-encoded in the MIME message body. When you send an email with a PDF attachment, the PDF is converted to Base64 and embedded in the email source with MIME headers. RFC 2045 sets a ceiling rather than a fixed width — lines must be no more than 76 characters each:

Content-Type: application/pdf; name="invoice.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="invoice.pdf"

JVBERi0xLjQKJeLjz9MKNiAwIG9iagoxIDAgb2JqCjw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyAy
IDAgUgo+PgplbmRvYmoK...

TLS/SSL certificates (PEM format)

The PEM format used for TLS certificates, private keys, and certificate chains wraps Base64-encoded DER data between header and footer lines. Lines are wrapped at 64 characters:

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAJC1HiIAZAiIMA0GCSqGSIb3Df...
-----END CERTIFICATE-----

When your server returns an SSL certificate error, one of the first debugging steps is to decode the certificate to check its subject, issuer, and expiry.

When NOT to use Base64

  • For security/encryption: Base64 provides no security. It is trivially reversible. Use AES-256 encryption when you need to protect data.
  • For large binary transfers: The 33% overhead is a fixed proportion, so the bigger the payload the more bandwidth and memory it costs you. Use multipart upload or binary protocols (gRPC, protobuf) instead.
  • For URL parameters: Standard Base64 uses + and /, which have special meaning in URLs. Use URL-safe Base64 (- and _) or percent-encode the standard Base64 output.

Base64 in Java — the three variants

// Standard Base64 — for general use
Base64.getEncoder().encodeToString(bytes)
Base64.getDecoder().decode(encoded)

// URL-safe Base64 — for URLs and filenames. Note it still emits = padding.
Base64.getUrlEncoder().encodeToString(bytes)
Base64.getUrlDecoder().decode(encoded)

// MIME Base64 — 76-char line breaks for email
Base64.getMimeEncoder().encodeToString(bytes)
Base64.getMimeDecoder().decode(encoded)

// URL-safe without padding — the form JWT/JOSE (RFC 7515) requires
Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)

Try it in the browser