Introduction
Hi, im quantinium
This is my notes website.
Backend
Understanding HTTP
HTTP is an application layer protocol designed to transfer information between client and servers. The protocol operates on a simple exchange i.e. a client sends a request describing the action on resources and the userver returns a response with the outcome. The resource is identified using Uniform Resource Locator(URL) and the action is the specified by an HTTP method.
- HTTP follows a client-server model i.e. a client sends a request to a server, and the server sends back a response.
- HTTP is stateless by design. Each request is self-contained and the server does not retain any memory of previous request by the same client. This allows HTTP to scale horizontally as any server behind a load balancer handles any request without needing shared state.
HTTP Message
An HTTP messages are the mechanism used to exchange data between a server and a client in HTTP protocol. There are two types of messages:
- Request: sent by client to trigger an action on server
- Response: answer sent by server in response to request.
HTTP Message Format
An HTTP message has the following format:
- A start line that describes the HTTP version along with the request method or the outcome of the request
- An optional set of HTTP headers containing metadata that describe the message
- An empty line indicating the metadata of message is complete
- An optional body containing data associated with the message.
The start line and headers of the HTTP message are collectively known as the head of the requests, and the part afterwards that contain its content is known as the body.
HTTP request
A HTTP request looks like
POST /users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
name=FirstName+LastName&email=bsmth%40example.com
Request Line
the start line in HTTP/1.x requests is called request-line and is made up of three parts
<method> <request-target> <protocol>
- Method: The HTTP method is one of the methods that define what the request must do.
- Request target: It is ussually an absolute or relative URL and helps to identify the resource.
- Protocol: This declares the HTTP version version used, which defines the structure of the remaining message, acting as an indicator of the expected version to use for the response.
Request Headers
Headers are metadata sent with a request after the start line and before the body. The Host header is the only required header in HTTP/1.1 and in HTTP/1.x, each header is a case-insensitive.
Request Body
The request body is the part of a request that carries information to the server. Only PATCH, POST and PUT requests have a body. The body can be of various formats such as text, json, etc whatever the server expects.
HTTP Response
Responses are the HTTP messages a server sends back in reply to a request. The response lets the client know what the outcome of the request was.
HTTP/1.1 201 Created
Content-Type: application/json
Location: http://example.com/users/123
{
"message": "New user created",
"user": {
"id": 123,
"firstName": "Example",
"lastName": "Person",
"email": "[email protected]"
}
}
Request Line
The start line is called a status-line in response and has three parts:
<protocol> <status-code> <reason-phrase>
- Protocol: The HTTP version of the message
- Status Code: The numeric status code that indicates the status of request such as 200 being success.
- Reason Phrase: The optional text after the status code is a brief, purely informational, text description of the status to help a human understand the outcome of a request
Response Headers
Response headers are the metadata sent with a response. In HTTP/1.x, each header is a case-insensitive string followed by a colon (:) and a value whose format depends upon which header is used. There are two types of headers:
- Response Headers: It give additional context about the message or add extra logic to how the client should make subsequent requests
- Representation Headers: if the message has a body, they describe the form of the message data and any encoding applied such as
text/html,application/json, etc. This allows a recipient to understand how to reconstruct the resources as it was before it was transmitted over the network.
Response Body
Request Body is included in most messages when responding to a client that tells the recipient about the action performed on the server by the request such as GET request has the data in the response body.
HTTP Methods
The method in the request line tell the server which action to perform on the resource. HTTP defines the following methods:
| Method | Purpose | Request has payload body | Response has payload body | Safe | Idempotent | Cacheable |
|---|---|---|---|---|---|---|
| GET | Retrieve a resource | Optional | Yes | Yes | Yes | Yes |
| HEAD | Retrieve headers only (no body) | Optional | No | Yes | Yes | Yes |
| POST | Submit data for processing such as adding entry in database | Yes | Yes | No | No | Yes* |
| PUT | Replace a resource entirely | Yes | Yes | No | Yes | No |
| DELETE | Remove a resource | Optional | Yes | No | Yes | No |
| CONNECT | Establish a TCP tunnel through an HTTP proxy | Optional | Yes | No | No | No |
| OPTIONS | Ask server what methods it supports | Optional | Yes | Yes | Yes | No |
| TRACE | Make server echo back the sent request for detecting if data was modified while being sent | No | Yes | Yes | Yes | No |
| PATCH | Partially update a resource | Yes | Yes | No | No | No |
NOTE: POST is technically cacheable per the HTTP spec, but almost never cached in practice. RFC 9110 says a POST response can be cached if the server explicitly includes the right headers, specifically Cache-Control or Expires, along with a Content-Location header that matches the request URI.
Status Code
The status code in the response indicates the outcome of the request. Status codes are grouped into five classes by their first digit
| Class | Range | Meaning |
|---|---|---|
| 1xx | 100–199 | Informational, request received, processing continues |
| 2xx | 200–299 | Success, request accepted and processed |
| 3xx | 300–399 | Redirection, further action needed |
| 4xx | 400–499 | Client error, malformed or unauthorized request |
| 5xx | 500–599 | Server error, valid request, server failed |
Note: This note is for
418 I'm a teapotenjoyers. Click for #cat
HTTP Versions
HTTP has gone through many iteration over the years such as:
HTTP/0.9 (1991)
The original, extremely primitive version. It had only one method, GET and no headers, no status codes, no metadata of any kind. You requested a path, and the server sent back raw HTML and closed the connection.
# Request
GET /page.html
# Response
<html>hello world</html>
HTTP/1.0 (1996)
This was the real first version of HTTP.
- Added Headers: It added headers for both request and response
- Status Codes: Added status codes
- Methods: Added more methods to the spec make the total methods 3 (GET, POST and HEAD)
- Content Type: Added support for more content type than HTML
- HTTP versioning: Added versioning to request line
HTTP/1.1 (1997)
It added the following features
- Persistent Connections (Keep-Alive): TCP connection stays open after a request, so multiple requests can reuse it.
- Pipelining: Client can send multiple requests without waiting for responses but responses must come back in order. This caused a serious problem called Head-of-Line (HOL) blocking
- Chunked Transfer Encoding: Server can stream a response in chunks without knowing the total size upfront.
- Host Header (mandatory): Since multiple websites can share one IP address (virtual hosting), the
Hostheader tells the server which site the client wants. - New Methods: Added new methods: PUT, DELETE, OPTIONS, TRACE, and CONNECT.
- Caching improvements:
Cache-Control,ETag,If-None-Match,If-Modified-Sincewere added.
HTTP/2 (2015)
It is a complete rewrite of how HTTP is transmitted, while keeping the same semantics (methods, headers, status codes all unchanged). Based on Google’s experimental SPDY protocol. It added the following features:
- Binary Framing: HTTP/1.x is plain text. HTTP/2 is binary. All communication is split into small frames and tagged with a type. Faster to parse, more compact, less error-prone.
[Text HTTP/1.1] [Binary HTTP/2]
GET /index HTTP/1.1 → 0x00 0x00 0x12 0x01 ...
Host: example.com
- Multiplexing: Multiple requests and responses travel over a single TCP connection simultaneously, each in their own numbered stream thus solving HOL.
- Header Compression (HPACK): HTTP/1.1 headers are repetitive plain text sent on every request. HPACK compresses them and maintains a shared table of previously seen headers on both ends, so repeated headers (like
User-Agent,Host,Cookie) are sent as tiny references instead of full strings. - Server Push: Server can proactively send resources the client hasn’t asked for yet. For example, when a browser requests
index.html, the server can pushstyle.cssandapp.jsimmediately, anticipating the browser will need them. - Stream Prioritization: Clients can assign priority weights to streams so critical resources (HTML, CSS) load before less important ones (analytics scripts).
HTTP/3 (2022)
HTTP/3 replacing TCP entirely with a new transport protocol called QUIC, which runs over UDP.
- QUIC (Quick UDP Internet Connections): Originally developed by Google, QUIC bakes in everything TCP provided (reliability, ordering, congestion control) but does it at the application layer on top of UDP.
- 0-RTT and 1-RTT Handshakes: TCP + TLS requires 2–3 round trips before data can flow. QUIC combines the transport and TLS handshake into one, needing just 1 round trip (1-RTT). For repeat connections to a known server, QUIC can send data in the very first packet i.e. 0-RTT.
- Connection Migration: TCP connections are tied to a 4-tuple (source IP, source port, dest IP, dest port). If you switch from Wi-Fi to mobile data, your IP changes and the connection breaks. QUIC uses a Connection ID instead, so connections survive network changes seamlessly.
- TLS 1.3 built in: QUIC mandates TLS 1.3 encryption.
HTTP Caching
Caching is the practice of storing a copy of a response so future requests can be served from that copy instead of hitting the server again. It reduces latency, saves bandwidth and reduces server load. There are two types of caches:
- Private Cache: It is a cache tied to a specific client, typically a browser cache. It is suitable for personalized content.
- Shared Cache: It is located between the client and server and can store responses that can be shared among users and shared caches can be further sub-classified into proxy caches and managed caches
- Proxy Cache: It is a cache that is operated by someone other than the website owner such as ISP, a coorporation, network admin. Since website owner doesnt control them, it can lead to cause issues such as providing stale data, caching responses that shouldnt be cached, etc. It has become ineffective due HTTPS and data being encrypted.
- Managed Cache: Managed caches are explicitly deployed by service developers to offload the origin server and to deliver content efficiently. Examples include reverse proxies, CDNs, and service workers in combination with the Cache API.
Heuristic Caching
HTTP is designed to cache as much as possible, so even if no Cache-Control is given, responses will get stored and reused if certain conditions are met. This is called heuristic caching. For example, take the following response. This response was last updated 1 year ago.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2021 22:22:22 GMT
<!doctype html>
It can be said that data that hasnt changed in a year has low prob. of change for some time. Therefore client stores this response despite lack of max-age. The spec. recommends about 10% of the time after storing. Heuristic caching is a workaround that came before Cache-Control support became widely adopted, and basically all responses should explicitly specify a Cache-Control header.
Fresh and State based on age
Stored HTTP responses have two states: fresh and stale. The fresh state usually indicates that the response is still valid and can be reused, while the stale state means that the cached response has already expired.The criterion for determining when a response is fresh and when it is stale is age. In HTTP, age is the time elapsed since the response was generated.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800
<!doctype html>
The cache that stored the example response calculates the time elapsed since the response was generated and uses the result as the response’s age. For the example response, the meaning of max-age is the following:
- If the age of the response is less than one week, the response is fresh.
- If the age of the response is more than one week, the response is stale
Expires or max-age
In HTTP/1.0, freshness used to be specified by the Expires header. The Expires header specifies the lifetime of the cache using an explicit time rather than by specifying an elapsed time.
Expires: Tue, 28 Feb 2022 22:22:22 GMT
Vary
The vary header tells caches to store seperate variants of the same URL based on specific request header values. A response with Vary:Accept-Encoding instructs the cache to key stored entries on the Accept-Encoding header. A client requesting br encoding and a client requesting gzip each get their own cached copy
Vary: Accept-Encoding, Accept-Language
Without Vary, a cache serves the same stored response to all clients regardless of request headers. This causes problems when the origin returns different representations based on encoding, language, or other negotiated features.Vary: * effectively disables caching. Every request is treated as unique
Validation
When a stored response becomes stale, the cache does not discard the response but cache sends a conditional request to the origin to check whether the resource is changed. This process is called revalidation.
Validators
Two validator mechanism exist:
Etag: Its an opaque identifier representing a specific version of the resource.Last-Modified: A timestamp indicating when the resource last changed. Has one-second resolution, making Etags the more reliable validator for rapidly changing resources.
Conditional request headers
The cache attaches validator to the revalidation request:
If-None-Matchsends the stored ETag. If the origin has the same ETag, the response has not changed. It takes more precedence.If-Modified-Sincesends the stored Last-Modified date. If the resource has not changed since the data, the origin confirms freshness.
Invalidation
Unsafe methods like POST, PUT and DELETE change server state. When a cache receives a non-error-response to an unsafe request, the cache must invalidate stored responses for the target URI. The cache also invalidates response for URI’s in the Location and Content-Location headers if they share the same origin. Invalidation marks stored responses as requiring revalidation.
Cache Groups
Cache Groups provide a mechanism for grouping related cached responses so a single unsafe request invalidates an entire set of resources. The Cache-Groups response header assigns a response to one or more named groups. The value is a list of case-sensitive strings.
HTTP/1.1 200 OK
Cache-Groups: "product-listings", "homepage"
The Cache-Group-Invalidation response header triggers invalidation of all cached responses belonging to the named groups. The header is processed only on responses to unsafe methods like POST or PUT.
HTTP/1.1 200 OK
Cache-Group-Invalidation: "product-listings"
After receiving this response, a cache invalidates all stored responses tagged with the “product-listings” group from the same origin. The invalidation does not cascade: invalidated responses do not trigger further group-based invalidations.
Cache-Control directives
The Cache-Control header carries directives controlling cache storage, reuse and revalidation. Directives appear in both request and response
Response Directives
| Directive | Effect |
|---|---|
max-age=N | Response is fresh for N seconds |
s-maxage=N | Overrides max-age in shared caches; implies proxy-revalidate |
no-cache | Cache stores the response but must revalidate before every reuse |
no-store | Cache must not store any part of the response |
public | Any cache stores the response, even for authenticated requests |
private | Only private caches store the response |
must-revalidate | Once stale, the cache must revalidate before reuse; serves 504 on failure |
proxy-revalidate | Same as must-revalidate for shared caches only |
no-transform | Intermediaries must not alter the response body |
must-understand | Cache stores the response only if the status code semantics are understood |
immutable | Response body does not change while fresh; skip revalidation on user-initiated reload |
stale-while-revalidate=N | Serve stale for up to N seconds while revalidating in the background |
stale-if-error=N | Serve stale for up to N seconds when revalidation encounters a 500–599 error |
Request Directives
| Directive | Effect |
|---|---|
max-age=N | Accept a response no older than N seconds |
max-stale[=N] | Accept a stale response, optionally no more than N seconds past expiry |
min-fresh=N | Accept a response fresh for at least N more seconds |
no-cache | Do not serve from cache without revalidating first |
no-store | Do not store the request or response |
no-transform | Intermediaries must not alter the body |
only-if-cached | Return a stored response or 504 |
CDN and edge caching
A CDN (content delivery network) operates a distributed network of shared caches at edge locations close to end users. CDN caches follow the same HTTP caching rules as proxy caches, with some platform-specific extensions.
Cache keys
CDN caches identify stored responses by a cache key, typically the request URL. Many CDNs extend the cache key with additional components: query string parameters, request headers (per Vary), Cookies, or geographic region. A misconfigured cache key is a common source of cache poisoning or unintended content sharing between users.
Cache purging
CDNs provide purging APIs to invalidate cached content before expiry. Purging by URL removes a single resource. Purging by tag (surrogate key) removes all responses tagged with a specific label. Tag-based purging is useful for invalidating all pages referencing a changed asset or data source.
Googlebot and HTTP caching
Google’s crawling infrastructure implements heuristic HTTP caching. Googlebot supports ETag / If-None-Match and Last-Modified / If-Modified-Since for cache validation when re-crawling URLs. When both validators are present, Googlebot uses the ETag value as the HTTP standard requires. The Cache-Control max-age directive helps Googlebot determine how often to re-crawl a URL. A page with a long max-age is re-fetched less frequently, while a page with a short max-age or no-cache signals the content changes often and warrants more frequent visits.
Common caching patterns
Versioned assets (cache forever)
Static assets with a fingerprint or version string in the URL are safe to cache indefinitely.
Cache-Control: max-age=31536000, immutable
The URL /assets/app.d9f8e7.js changes whenever the file content changes. The immutable directive tells the browser not to revalidate even on a user-initiated reload.
HTML pages (always revalidate)
HTML pages change frequently and benefit from revalidation on every load.
Cache-Control: no-cache
The cache stores the response but checks with the origin before serving. Combined with a strong ETag, this pattern ensures fresh content with minimal transfer cost when nothing changed.
Sensitive content (never cache)
Login pages, banking portals, and other pages with private data must not be cached.
Cache-Control: no-store
Shared cache with revalidation fallback
API responses served through a CDN with graceful degradation during outages.
Cache-Control: s-maxage=300, stale-if-error=3600
The CDN caches the response for five minutes. On origin failure, the CDN serves stale content for up to one hour.
HTTP Content Negotiation
Content negotiation is the mechanism by which a client and server agree on the best representation of a resource. The server selects from available variants based on client preferences expressed through HTTP headers, or presents alternatives for the client to choose from. A single resource from a URI can contain various representation such as json, xml, png, avif, etc depending upon the resource. Three negotiation patterns exists to determine which variet to send to client:
- Proactive negotation: the server picks the best representation using preferences the client sent in the request. This is the dominant pattern using
Accept,Accept-Encoding,Accept-CharsetandAccept-Languageheaders - Reactive negotiation: the server lists available representations and the client picks one.
- Request content negotiation: the server advertises preferences in a response, influencing how the client formats subsequent requests
HTTP Compression
HTTP compression reduces the size of data transferred between servers and clients. A client sends an Accept-Encoding header listing supported content codings. The server picks one, compresses the response body, and indicates the choice in a Content-Encoding header. This exchange is a form of proactive content negotiation.
The Accept-Encoding request header lists acceptable codings with optional quality values:
Accept-Encoding: br, gzip;q=0.8, zstd;q=0.9
The server selects one coding and returns the compressed body with two key headers:
- Content-Encoding names the coding applied
- Content-Length reflects the compressed size, not the original
Content Encoding
The most common ones are:
- gzip: The gzip coding uses the GZIP file format , combining LZ77 and Huffman coding. Supported by every HTTP client and server. The gzip coding remains the most widely deployed content coding on the web.
- br(Brotli): The br coding uses the Brotli algorithm , developed by Google. Brotli achieves higher compression ratios than gzip at comparable decompression speeds. Brotli includes a built-in static dictionary of common web content patterns, an advantage for compressing HTML, CSS, and JavaScript.All major browsers support Brotli over HTTPS connections.
- zstd(Zstandard): The zstd coding uses the Zstandard algorithm , developed at Facebook. Zstandard offers a wide range of compression levels, from fast modes exceeding gzip speed to high modes rivaling Brotli compression ratios. Zstandard decompression is fast regardless of the compression level used. When used as an HTTP content coding, Zstandard encoders must limit the window size to 8 MB and decoders must support at least 8 MB. This cap prevents excessive memory consumption in browsers and other HTTP clients
- dcb(Dictionary-Compressed Brotli): The dcb coding compresses a response using Brotli with an external dictionary. The compressed stream includes a fixed header containing the SHA-256 hash of the dictionary, allowing the client to verify the dictionary matches before decompression. Dictionary-based Brotli supports compression windows up to 16 MB.
- dcz(Dictionary-Compressed Zstandard): The dcz coding compresses a response using Zstandard with an external dictionary. Like dcb, the compressed stream starts with a header containing the SHA-256 hash of the dictionary. The header is structured as a Zstandard skippable frame, making the format compatible with existing Zstandard decoders.
- deflate: The deflate coding wraps a DEFLATE compressed stream inside the zlib format . Historical inconsistencies between implementations (some sent raw DEFLATE without the zlib wrapper) made deflate unreliable. Modern HTTP favors gzip or br instead.
- compress: The compress coding uses adaptive Lempel-Ziv- Welch (LZW). Rarely encountered in modern HTTP.
- identity: The identity coding means no transformation was applied. This value appears in Accept-Encoding to signal acceptance of uncompressed responses. A server is always allowed to send an uncompressed response, even when the client lists only compression codings.
Routing
Routing is how a server decides what to do with an incoming request. Every request has two key pieces of information:
- the HTTP method
- URL path
GET /api/users
───┬─── ────┬───
│ └── WHERE (route path)
└─────────── WHAT (HTTP method)
│
▼
Router matches → runs the correct handler function
Static Routes
Static routes have a fixed, unchanging path. Every part of the URL is a literal string.
GET /api/books → return all books
POST /api/books → create a new book
GET /api/users → return all users
Dynamic Routes
Dynamic routes contain variable segments in the URL path, denoted by a colon(:). These variables let you target a specific resource by its identifier. The server extracts the dynamic value from the URL and uses it in its logic
GET /api/users/:id
GET /api/users/123 → id = "123"
GET /api/users/456 → id = "456"
GET /api/users/abc → id = "abc"
Query Parameters
Query parameters are key-value pairs appended to the URL after a ?. They are used for optional, non-semantic data like filtering, sorting, and pagination. Multiple query params are separated by &. They are especially important for GET requests since GET has no request body
GET /api/books?page=2
GET /api/search?query=javascript&sort=newest
GET /api/products?category=shoes&minPrice=50&maxPrice=200
Nested Routes
Nested routes express hierarchical relationships between resources by embedding multiple identifiers in a single path
/api/users/123/posts/456
│ │
│ └── Post ID 456 belonging to user 123
└── User ID 123
/api → base path
/users → static, "users" collection
/123 → dynamic, specific user (path param)
/posts → static, "posts" collection under that user
/456 → dynamic, specific post (path param)
Route versioning
As APIs evolve, you often need to make breaking changes such as changing response shapes, renaming fields, removing endpoints. If you change an existing endpoint, every client using it breaks. Versioning solves this by running multiple versions of the API simultaneously
GET /api/v1/products → old format, still running for existing clients
GET /api/v2/products → new format, for new clients
Common versioning strategies beyond URL versioning include passing the version as a header API-Version: 2 or as a query param ?version=2, but URL versioning is the most common and explicit.
Catch-All Routes
A catch-all (wildcard) route is a fallback handler that matches any request that didn’t match any defined route. It prevents the server from returning a confusing raw error. For example the following code returns 404 on every route that is not registered before it.
app.all('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
Working of a router
When a request comes in, the router iterates through registered routes in order and pattern-matches the method + path:
Incoming: GET /api/users/123/posts
Registered routes:
GET /api/users → no
POST /api/users → no (wrong method)
GET /api/users/:id → no (path too long)
GET /api/users/:id/posts → yes MATCH → run handler
The router extracts dynamic segments, populates req.params, and calls the handler. If nothing matches, the catch-all fires.
Serialization and Deserialization
Different programming languages store data in their own native formats:
- JavaScript → Objects
- Rust → Structs
- Python → Dicts
- Go → Structs These formats live in memory and are language-specific. You can’t just send a JS object over a network or any boundary to another system that wouldnt understand js object such as a rust or go server. There we agree on a neutral, language-agnostic format. Both sides can convert to/from this format. This process is called serialization and deserialization.
Serialization
This is the act of flattening your in-memory, language specific data structure into a transferrable sequence of bytes. The client converts its native data into agreed format before sending it.
const user = {
name: "quantinium",
age: 21,
scores: [98, 87, 91],
active: true
};
// Serialization → convert to a JSON string (bytes, language-agnostic)
const payload = JSON.stringify(user);
// '{"name":"quantinium","age":21,"scores":[98,87,91],"active":true}'
fetch("/api/user", {
method: "POST",
body: payload
});
Serialization Standards
You can use anything as a transfer format as long as both sides have a serializer and deserializer for it. Standards fall into two categories:
Text based
Human-readable, easy to debug, universally supported. Slower and larger than binary.
| Format | Used For |
|---|---|
| JSON | REST APIs, web — the dominant standard |
| XML | Enterprise, SOAP, legacy systems |
| YAML | Config files (Docker, Kubernetes, GitHub Actions) |
| TOML | Config files (Cargo.toml, pyproject.toml) |
| CSV | Tabular data exports, spreadsheets |
Binary
Not human-readable. Significantly smaller and faster. Used when performance matters.
| Format | Used For |
|---|---|
| Protobuf | gRPC, internal microservices (by Google) |
| Avro | Kafka, data pipelines, data lakes |
| MessagePack | Real-time apps, Redis internals, gaming |
| FlatBuffers | Games, mobile — zero-copy deserialization |
Deserialzation
The reciever get those bits and reconstructs them into its own native type.
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct User {
name: String,
age: u32,
scores: Vec<u32>,
active: bool,
}
async fn create_user(Json(user): Json<User>) -> impl IntoResponse {
println!("{}", user.name);
}
Schema/Contract
Both sides need to agree on the shape of the data. This agreement is called a schema or contract
- Implicit schema — both sides just follow a convention. Fragile, breaks silently.
- Explicit schema — tools enforce the shape at the boundary.
- Protobuf → .proto file shared between services
- JSON Schema / OpenAPI → describes and validates JSON structure
- Zod (TypeScript) → runtime validation on received data
Full Round Trip
CLIENT (JavaScript) SERVER (Rust)
─────────────────── ─────────────
JS Object in RAM
{ name: "quantinium", age: 21 }
│
│ JSON.stringify() ← SERIALIZE
▼
"{"name":"quantinium","age":21}"
│
│ HTTP POST (bytes over TCP)
▼
"{"name":"quantinium","age":21}"
│
│ serde_json::from_str() ← DESERIALIZE
▼
User { name: "quantinium", age: 21 }
│
│ (process the request, build response)
│
User { name: "quantinium", verified: true }
│
│ serde_json::to_string() ← SERIALIZE
▼
"{"name":"quantinium","verified":true}"
│
│ HTTP 200 (bytes over TCP)
▼
"{"name":"quantinium","verified":true}"
│
│ JSON.parse() ← DESERIALIZE
▼
JS Object in RAM
{ name: "quantinium", verified: true }
Authentication and Authorization
Authentication
Authentication is the process of verifying identity. It confirms that users are who they claim to be. Common methods are:
- Username and Password
- Biometrics
- Multi-Factor authentication
- Security tokens or keys
- Single sign-on(SSO)
Authorization
It is the process of determining what permission on authenticated user has. It happens after authentication and control access to resources. Common implementation are:
- Role-based access control(RBAC)
- Permission lists
- Access control lists (ACL’s)
- Attribute-based access control(ABAC)
The stateless problem
HTTP is inherently stateless, meaning the server treats every request as new. It processes the request and immediately forgets it. For example, on a website, we log in with our username and password to authenticate ourselves. The easiest way would be to send our credentials with every request, but this is insecure and inefficient. Therefore, various authentication methods such as session auth, jwt auth, etc have been created to solve this problem.
Types of Authentication
By Factor Type
- Single Factor Authentication(SFA): Authentication using only one factor such as password, pin, etc.
- Two Factor Authentication(2FA): Requires two different types of factors to authenticate. For example: password and code from authenticator app/SMS/Email.
- Multi Factor Authentication(MFA): Uses two ore more factors such as password + SMS/authenticator app/ or qrcode.
By Method/Technology
- Password based authentication: This is ours traditional username/password authentication.
- Password-less authentication: This uses different ways to authenticate users such as magic link, OTP, Biometrics, etc.
- Biometric Authentication: It uses physical characteristics of user such as thumbprint, faceid, etc.
- Token based authentication: Using cryptographically signed tokens such as JWT’s
- Session based authentication: Uses session tokens to create and verify session for users.
- Certificate based authentication: Uses digital certificates to prove identity.
- API Key authentication: Using a unique string to authenticate API requests
- Single Sign-ON (SSO): Allows access to multiple applications through one set of credentials
- OAuth 2.0/OpenID Connect: Let a trusted third part handle authentication such as google, github, etc and recieve a token proving user’s identity.
- SAML(Security Assertion Markup Language): XML-based SSO standard(older than OAuth).
- Kerberos: Network authentication protocol using tickets rather than passwords.
- LDAP(Lightweight Directory Access Protocol): Protocol for accessing and managing directory services(like phone book for users)
- Smart Card Authentication: Physical card with embedded chip containing cryptographic keys.
- OTP based: OTP is sent using SMS or authenticator app.
- FIDO2/WebAuthn: Modern passwordless standard using public-key cryptography. Used in Yubikeys, Google Titan key, etc
- Magic link: Passwordless login via email link
Session-Based Authentication
Session-based authentication is a stateful mechanism for verifying identity. It relies on three main components:
- Session Storage: Active session data is stored server-side (typically in memory, or a database like Redis).
- Session Cookie: The client stores a unique, opaque identifier in a cookie. This is automatically attached to every request by the browser.
- Server-Side Validation: The server identifies the user by matching the incoming ID against its internal store.
The Authentication Workflow
- Handshake: The user sends credentials (e.g., username/password) to the server.
- Creation: Upon successful login, the server generates a unique Session ID, stores it in a database/cache, and maps it to the user’s profile.
- Delivery: The server sends back a
Set-Cookieheader containing the ID. - Verification: For every subsequent request, the browser sends the cookie. The server performs a lookup to retrieve the associated user data.
Advantages
- Granular Control: You can instantly invalidate a session (log a user out) by deleting the ID from your server-side store.
- Security: Since the ID is an opaque token, no sensitive user data is exposed in the token itself.
- Efficiency: The payload is extremely small (just a random string), minimizing bandwidth.
Disadvantages
- Scalability (The “State” Problem): In distributed systems with multiple servers, you must use a centralized session store (like Redis) so all servers can recognize the same ID.
- Resource Overhead: Every request requires an I/O operation (database or cache lookup) to verify the session.
- CSRF Risk: Because browsers send cookies automatically, you must implement anti-CSRF measures (like
SameSitecookie attributes or CSRF tokens).
JWT
OAUTH
What is System Design?
System design is the process of defining how different parts of a software system interacts to meet both functional and non-functional requirements.
Key components of a system
A typical software system can be broken down into several key components:
- Client - The part of the system with which the user interacts. It is responsible for displaying information, collecting user input and communicating with the backend.
- Server - The backend handles the core functionality of the system. It processes incoming requests, executes business logic, interacts with databases and other services, and send requests back the client.
- Databases - This is responsible for storing and managing data. It can be of various types such as relational/non-relational database, in-memory database, distributed object storage systems, etc.
- Networking Layer - This include components like load balancers, API’s, communication protocol that ensure reliable and efficient interaction between different part of system
- Third-Party Services - These are external API’s or platform that extend the system’s capabilities such as payment processors, email or sms services, authentication services, etc.
Process of System Design
System design is a step by step process that starts with understanding the requirements and ends with a detailed blueprint.
1. Requirement Gathering
This includes asking question about the requirements of the product which include:
- Functional Requirements
- Non-Functional Requirements
- Number of users expected
- Expected data volume, traffic patterns
- Constraints such as budget, compliance rules, etc
2. Making Approximate Calculations
This involves making approximation about the number of resources would be needed.
- Data size
- Requests per second
- Bandwidth needs
- Number of resources required
3. High Level Design
Now that we have an overview of what our system will need, we can create a visual or high level design of the system’s components and how they interact with each other. This includes:
- The main modules and services
- Data flow between them
- External Dependencies (eg. third-party API’s, external databases)
4. API Design
Once the high level design is done, we can move on to define the components in detail such as:
- Type of database used
- Schema, tables and relationships
- Design API’s for services.
5. Deep Dive
Here we zoom into each component and define:
- Internal logic, caching, concurrency handling
- Scaling strategies
- Replication, partitioning and fault tolerance
- Availability, reliability, latency
- Non functional requirements
6. Identify Bottlenecks and Trade-offs
In this phase we see the bottlenecks of our designed systems and the trade-offs we made. We make sure to try to eliminate bottlenecks if reasonable and important and reduce and justify the trade-offs if made in the desing.
Scalability
Java
- class - a blueprint or template defining the structure and behavior of objects
- object - an instance of a class representing a specific identity
- encapsulation - bundling data and methods witihin a class, restricting direct access to data using access modifiers (public, private, protected).
- inheritance - mechanism where a class inherits properties and methods of another class.
- polymorphism - it means many forms. it means when methods of a class can morph into a different function with the help of inheritence: method overloading and overriding.
- Abstraction - hiding complex implementation details and exposing only necessary features. implemented using abstract class and interfaces.
- Decomposition - process of breaking a complex system into more maneagable and simple parts.
- Abstraction Mechanism - techniques to achieve abstraction
- parameterization - making a component accepts parameters.
- specification - defining behavior and properties of a component.
Kinds of Abstraction
- Procedural Abstraction - focuses on abstracting a single procedure or action into a single component
- Data Abstraction - Hiding internal representation of data and exposing only necessary operations. ex - stack , queue, etc.
- Control Abstraction - Abstract control flow intro reusable constructs. ex- map, forEach, etc
- Type Abstraction - Generalizes data types enabling to work with self-defined and more complex data types.
- Functional Abstraction - Treats function as first class entities, abstracting behavior that can be passed or composed.
- Module Abstraction - Group related procedures and data into a single unit.
Benefits
- modularity - the gang of four
- reusability - inheritance and polymorphism allows code reuse
- scalability - code organization helps in scaling to large codebases (netflix btw)
- maintainability - changes to one class doesnt affect others. thus simplifying updates
- real world modelling - oop mirrors real world by having objects and methods.
- flexibility
- security
Evolution
- 1960s - early programming was procedural, mostly was c, fortran, algol, etc
- 1967 - Simula 67, developed by Ole-Johan Dahl and Kristen Nygaard, introduced the concept of classes and objects, laying the foundation for OOP.
- 1970s - Smalltalk, developed by Alan Kay at Xerox PARC, fully embraced OOP principles, introducing inheritance, polymorphism, and message passing.
- 1980 - 1980s: OOP gained traction with languages like C++ (Bjarne Stroustrup), which added OO features to C, and Objective-C
- 1990 - Java (Sun Microsystems) simplified OOP for cross-platform development, emphasizing portability and safety.
- 2000s–Present: Modern languages like Python, Ruby, and C# integrated OOP with other paradigms (e.g., functional programming). Frameworks and libraries (e.g., Spring, Django) further standardized OO practices.
Programming Paradigms
Programming paradigms define the style and methodology to write code. Some of the major paradigms are :-
- Procedural -
- focuses on procedures or data that operate on data.
- program is a sequence of tasks.
- ex - c, fortran
- Object Oriented -
- Organized code around objects, which combine data (attributes) and operations (methods).
- ex - c++, java, python
- Functional -
- treats computation as the evaluation of mathematical functions, avoiding state changes and mutable data.
- ex - haskell, lisp
Procedural vs OOP
| Aspect | Object-Oriented Approach | Procedure-Oriented Approach |
|---|---|---|
| Basic Unit | Objects (instances of classes) | Functions or procedures |
| Focus | Data and how it is manipulated (data-centric) | Procedures and sequence of tasks (process-centric) |
| Structure | Programs are organized around classes and objects | Programs are divided into functions |
| Data Handling | Data is encapsulated (private/protected) | Data is often global, accessible to all functions |
| Modularity | High, due to encapsulation and classes | Moderate, relies on function separation |
| Reusability | High, via inheritance and polymorphism | Limited, requires rewriting or copying code |
| Scalability | Scales well for large, complex systems | Becomes cumbersome in large systems |
| Security | Better, due to encapsulation and access control | Lower, as data is often exposed |
| Examples | Java, C++, Python (OO mode) | C, Fortran, Pascal |
| Real-World Modeling | Naturally models real-world entities (e.g., Car, Person) | Less intuitive for complex entities |
| Maintenance | Easier to modify and extend | Harder, as changes may affect multiple functions |
Basic Features
java is
- high level
- object oriented - built around object and classes
- platform-independent - due to jvm
- robust - proper types, exception handling, garbage collection
- secure - sand boxed, byte-code execution and security manager to ensure safe execution
- multi-threaded
- released in 1995 by sun micro-systems.
use cases
- web application - spring-boot, hibernate
- mobile apps
- enterprise software - Netflix BTW
- big data - Hadoop
- embedded and iot
features
- simple and familiar syntax to c
- no pointers and manual memory management
- object oriented
- platform independent due to jvm
- robust
- secure
- multithreaded
- high performance due to JIT
- portable
- architecture neutral
- dynamic
- rich std library
JVM
java virtual machine is an abstract computing machine that serves as runtime environment for executing java bytecode
- translates java bytecode into machine code for execution
- manages memory, security and execution of java application
- provides a consistent environment across different hardware and operating systems.
Architecture
- Class Loader Subsystem -
- Loading - loads, links and initializes
.classfiles into memory. - Linking- ensure bytecode is safe for execution -> allocate memory for static variables and assign default values -> resolve symbolic links to direct references.
- Initialization - executes static initializers and assigns values to static fields.
- Loading - loads, links and initializes
- Runtime Data Areas -
- Method area - stores class level data, class structures, method bytecode, constant pool, static vars. shared accross all threads.
- Heap - stores all objects and their instance vars.
- Stack - per-threaded memory for method calls, storing stack frames.
- PC (program counter) - per-threaded register that stores the address of the current instruction being executed.
- Native method stack - per-thread memory for native methods.
- Execution Engine -
- executes bytecode by translating it into machine code.
- components -
- interpreter - executes bytecode line by line
- just in time compiler (JIT) - compiles frequently executed code for faster execution.
- garbage collector - automatically reclaim memory from unused object in the heap.
- Native method interface - enables java code to interact with native application in c or c++
- Security Manager - enforces security policies, restricted untrusted code from accessing sensitive resources.
OOP
its a paradigm based on concept of objects which combines data (attributes) and behavior (methods).
class
it is a blueprint or template for creation of an object. it defines - attributes - data stored in objects (variables). - methods - behaviors or functions that object can perform
- a class encapsulates data and behavior, supporting oop principles like encapsulation, inheritance and polymorphism.
- contains fields, methods, constructors and optionally other classes and interfaces.
class Car {
// Fields
String model;
int year;
// Constructor
Car(String model, int year) {
this.model = model;
this.year = year;
}
// Method
void displayInfo() {
System.out.println("Model: " + model + ", Year: " + year);
}
}
creating objects
- an object is an instance of a class, created from class blueprint.
- objects are created using
newkeyword which allocated memory to the heap. - a constructor is called during object creation to initlialize fields.
- each object has its own copy of instance variables but shares the class’s methods.
public class Main {
public static void main(String[] args) {
// Creating objects
Car car1 = new Car("Toyota", 2020);
Car car2 = new Car("Honda", 2022);
// Accessing object methods
car1.displayInfo(); // Outputs: Model: Toyota, Year: 2020
car2.displayInfo(); // Outputs: Model: Honda, Year: 2022
}
}
assigning object reference variables
- variables of a class type are references to object.
- a reference variable holds the memory address of an object on the heap.
- assigning one variable to another variables make both point to the same object
- objects are not duplicated during assignment. only references are copied
- setting a references to null removes its association with any object, making the object eligible for garbage collection.
public class Main {
public static void main(String[] args) {
// Create objects
Car car1 = new Car("Toyota", 2020);
Car car2 = new Car("Honda", 2022);
// Assigning object reference
Car ref = car1; // ref now points to the same object as car1
ref.displayInfo(); // Outputs: Model: Toyota, Year: 2020
// Modify object via ref
ref.model = "Nissan";
car1.displayInfo(); // Outputs: Model: Nissan, Year: 2020 (car1 reflects change)
// Set car1 to null
car1 = null;
ref.displayInfo(); // Still works: Model: Nissan, Year: 2020 (ref still points to object)
// car1.displayInfo(); // Would throw NullPointerException
}
}
Access Modifiers
- public: Inherited and accessible everywhere.
- protected: Inherited and accessible in the same package or subclasses (even in different packages).
- default (no modifier): Inherited only within the same package.
- private: Not inherited or accessible in the subclass directly.
Inheritance
- it is an oop concept that allows a class to inherit properties and behaviors from another class.
- It promotes code reusability, modularity and establishes a hierarchical relationship between classes
- the
extendskeyword is used to establish inheritance.
class Superclass {
// Fields and methods
}
class Subclass extends Superclass {
// Additional fields and methods, or overrides
}
- purpose :
- reusability - reuse existing code from superclass.
- extensibiliy - add new features or modify inherited behavior in the subclass.
- polymorphism - enable polymorphic behavior where a superclass reference can point to a subclass object.
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println(name + " is eating.");
}
}
class Dog extends Animal {
Dog(String name) {
super(name); // Call superclass constructor
}
// Override eat method
@Override
void eat() {
System.out.println(name + " is eating bones.");
}
// New method
void bark() {
System.out.println(name + " is barking.");
}
}
class Main {
public static void main(String[] args) {
Dog dog = new Dog("Buddy");
dog.eat(); // Output: Buddy is eating bones.
dog.bark(); // Output: Buddy is barking.
}
}
types of inheritance
- single inheritance - a subclass inherits from a superclass
class A {}
class B extends A {} // B inherits from A
- multilevel inheritance - a class inherits from a superclass which further inherits from another class.
class A {}
class B extends A {}
class C extends B {} // C inherits from B, which inherits from A
- hierarchical inheritance - multiple classes inherit from the same subclass``
class A {}
class B extends A {}
class C extends A {} // B and C both inherit from A
- multiple inheritance - a class inherits from multiple super classes (is not supported in java via class). is done using interfaces
interface I1 {}
interface I2 {}
class C implements I1, I2 {} // Valid
- hybrid inheritance - a combination of two or more types of inheritance.
limitations
- no multiple inheritance
- tight coupling - overuse of inheritance can lead to tightly coupled code leading to harder maintainability
- fragile base case problem - changes to the superclass can unintentionally break subclasses
class Vehicle {
String brand;
int speed;
Vehicle(String brand, int speed) {
this.brand = brand;
this.speed = speed;
}
void move() {
System.out.println(brand + " is moving at " + speed + " km/h.");
}
}
class Car extends Vehicle {
int doors;
Car(String brand, int speed, int doors) {
super(brand, speed);
this.doors = doors;
}
@Override
void move() {
System.out.println(brand + " car with " + doors + " doors is moving at " + speed + " km/h.");
}
}
class Main {
public static void main(String[] args) {
Vehicle vehicle = new Car("Toyota", 120, 4);
vehicle.move(); // Output: Toyota car with 4 doors is moving at 120 km/h.
}
}
Polymorphism
- polymorphism allows a single reference (a superclass or interface) to refer to objects of different subclasses, with specific behavior determined by the actual object type at runtime or compile time.
- it enables code to work with objects in generalized way while allowing specific implementation to vary.
- purpose
- flexibility - write generic code that works with multiple types.
- extensibility - add new subclasses without modifying existing code.
- reusability - use a common interface or superclass to handle diverse objects.
types of polymorphism
- runtime polymorphism - achieved through method overriding, where the method to be called is determined at runtime based on actual object type.
- compile time polymorphism - achieved using method overloading or operator overloading where method is chosed at compile time based on method signature.
runtime polymorphism
- achieved through method overriding, where a subclass provides a specific implementation of a method defined in superclass or interface.
- jvm determines the actual type of object at runtime and invokes the overridden method of subclass, even if the reference type is the superclass or interface.
class Animal {
void sound() {
System.out.println("Generic animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Woof");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Meow");
}
}
class Main {
public static void main(String[] args) {
Animal animal1 = new Dog(); // Superclass reference, Dog object
Animal animal2 = new Cat(); // Superclass reference, Cat object
animal1.sound(); // Output: Woof (Dog's method called)
animal2.sound(); // Output: Meow (Cat's method called)
}
}
compile time polymorphism
- achieved using method overloading, where multiple methods in the same class have the same name but different parameters lists.
- the compiler determines which method to call at compile time based on method signature and arguments passed.
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3)); // Output: 5
System.out.println(calc.add(2.5, 3.5)); // Output: 6.0
System.out.println(calc.add(1, 2, 3)); // Output: 6
}
}
advantages
- flexibility - write generic code that works with multiple types
- extensibility - add new subclasses or implementations without modifying existing code
- maintainability - reduces code duplication by using common interface or superclasses.
limitations
- performance overhead - runtime polymorphism involves dynamic method dispatch, which is slightly lower than static binding.
- complexity - overuse of polymorphism can make code harder to understand.
- downcasting risks - incorrect downcasting can lead to
classCastException
Inheritance vs Polymorphism
| Aspect | Inheritance | Polymorphism |
|---|---|---|
| Definition | A mechanism where a class (subclass) inherits fields and methods from another class (superclass). | A mechanism allowing objects of different classes to be treated as instances of a common superclass or interface. |
| Purpose | Enables code reuse and establishes an “is-a” relationship between classes. | Enables flexibility by allowing a single interface to represent different types or behaviors. |
| Mechanism | Achieved using the extends keyword for classes or implements for interfaces. | Achieved through method overriding (runtime) or method overloading (compile-time). |
| Relationship | Creates a hierarchical relationship (e.g., Dog is an Animal). | Allows objects to be processed uniformly despite different implementations (e.g., Animal reference for Dog or Cat). |
| Type | Structural concept (defines class relationships). | Behavioral concept (defines how objects behave or are accessed). |
| Code Example | java<br>class Animal {<br> void eat() { System.out.println("Eating"); }<br>}<br>class Dog extends Animal {}<br> | java<br>class Animal {<br> void sound() { System.out.println("Sound"); }<br>}<br>class Dog extends Animal {<br> @Override<br> void sound() { System.out.println("Woof"); }<br>}<br>Animal a = new Dog();<br>a.sound(); // Outputs: Woof<br> |
| Execution Time | Determined at compile time (class structure is fixed). | Runtime (method overriding) or compile-time (method overloading). |
| Flexibility | Fixed hierarchy; adding new classes requires modifying the structure. | Highly flexible; new subclasses can be added without changing existing code. |
| Dependency | Does not require polymorphism (can exist without overriding). | Often relies on inheritance (for method overriding) or interfaces. |
| Scope | Focuses on sharing and extending code (fields, methods). | Focuses on dynamic behavior or method selection. |
Relationship Between Inheritance and Polymorphism
- Dependency: Polymorphism (specifically runtime polymorphism via method overriding) often relies on inheritance, as it requires a superclass-subclass relationship or interface implementation. However:
- Inheritance can exist without polymorphism (e.g., a subclass uses inherited methods without overriding them).
- Polymorphism can occur without inheritance in the case of interfaces (e.g., a class implements an interface but doesn’t extend a class).
- Complementary Roles:
- Inheritance provides the structure (class hierarchy) for code reuse.
- Polymorphism provides the behavior (dynamic method invocation) for flexibility.
Abstract Classes
- it is a class in java that can’t be instantiated but is used as a blueprint for other classes. it is designed to be extended by subclasses.
- it is declared using keyword
abstract. - it may contain
- abstract methods - methods without a body, must be implemented by subclass
- concrete methods - methods with implementation that subclasses use or override.
- fields - constructors and nested classes.
- cannot be instantiated.
- purpose -
- provide a common structure and behavior for related classes.
- enforces a contract by requiring subclasses to implement abstract methods.
- enables code reuse through shared fields and methods.
- support polymorphism by allowing abstract class references to point to subclass objects.
abstract class Animal {
String name;
// Constructor
Animal(String name) {
this.name = name;
}
// Abstract method (must be implemented by subclasses)
abstract void makeSound();
// Concrete method (shared by all subclasses)
void sleep() {
System.out.println(name + " is sleeping.");
}
}
class Dog extends Animal {
Dog(String name) {
super(name); // Call superclass constructor
}
@Override
void makeSound() {
System.out.println(name + " says Woof!");
}
}
class Cat extends Animal {
Cat(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + " says Meow!");
}
}
class Main {
public static void main(String[] args) {
// Animal animal = new Animal("Generic"); // Error: Cannot instantiate
Animal dog = new Dog("Buddy");
Animal cat = new Cat("Whiskers");
dog.makeSound(); // Output: Buddy says Woof!
dog.sleep(); // Output: Buddy is sleeping.
cat.makeSound(); // Output: Whiskers says Meow!
cat.sleep(); // Output: Whiskers is sleeping.
}
}
Interfaces
- interfaces in java is a reference type that defines a set of abstract methods that a class must implement.
- it specifies what a class must do without dictating how it should do it.
- features -
- declared using
interfacekeyword. - all methods in an interface are implicitely
pubilcandabstract. - can contain constants
- cannot instantiate directly.
- supports multiple inheritance
- declared using
interface InterfaceName {
// Constant
int CONSTANT = 10;
// Abstract method
void methodName();
// Default method (Java 8+)
default void defaultMethod() {
System.out.println("Default implementation");
}
// Static method (Java 8+)
static void staticMethod() {
System.out.println("Static method");
}
}
implementing interfaces
- a class implements an interface using
implementskeyword, agreeing to provide implementations for all abstract methods defined in the interface. - rules -
- a class must implement all abstract methods of an interface, or it must be declared
abstract. - a class can implement multiple interfaces.
- the implementing class must use
publicaccess for interface methods (since interfaces methods are implicitlypublic) - interfaces support polymorphism, an interface reference can point to any implementing class’s object.
- a class must implement all abstract methods of an interface, or it must be declared
interface Movable {
void move(); // Abstract method
default void stop() {
System.out.println("Stopping...");
}
}
class Car implements Movable {
@Override
public void move() {
System.out.println("Car is moving.");
}
}
class Bike implements Movable {
@Override
public void move() {
System.out.println("Bike is moving.");
}
}
class Main {
public static void main(String[] args) {
Movable car = new Car();
Movable bike = new Bike();
car.move(); // Output: Car is moving.
car.stop(); // Output: Stopping...
bike.move(); // Output: Bike is moving.
bike.stop(); // Output: Stopping...
}
}
features
- default methods - methods with a body. can be overriden.
interface Vehicle {
void drive();
default void honk() {
System.out.println("Beep beep!");
}
}
class Truck implements Vehicle {
@Override
public void drive() {
System.out.println("Truck is driving.");
}
}
class Main {
public static void main(String[] args) {
Truck truck = new Truck();
truck.drive(); // Output: Truck is driving.
truck.honk(); // Output: Beep beep!
}
}
- static methods - belong to the interface itself not instances
interface Utility {
static void helper() {
System.out.println("Static helper method.");
}
}
class Main {
public static void main(String[] args) {
Utility.helper(); // Output: Static helper method.
}
}
- constants - fields in an interface are implicitly
public,static,finalkeyword.
Abstract Classes vs Interfaces
| Feature | Interface | Abstract Class |
|---|---|---|
| Definition | A contract with abstract methods, default methods, static methods, and constants. | A class with abstract and/or concrete methods, fields, and constructors. |
| Keyword | interface | abstract class |
| Instantiation | Cannot be instantiated. | Cannot be instantiated. |
| Methods | Abstract methods (implicitly public), default methods, static methods. | Abstract and concrete methods (any access modifier). |
| Fields | Only public, static, final fields (constants). | Can have instance fields (state) with any access modifier. |
| Constructors | No constructors. | Can have constructors for initialization. |
| Inheritance | A class can implement multiple interfaces (implements). | A class can extend only one abstract class (extends). |
| Multiple Inheritance | Supported (via multiple interfaces). | Not supported (single inheritance for classes). |
| Access Modifiers | Methods are implicitly public. | Methods/fields can be public, protected, private, or default. |
| State | Stateless (no instance fields). | Can maintain state (instance fields). |
| Use Case | Define a contract or role (e.g., Comparable, Runnable). | Provide partial implementation and shared state (e.g., Vehicle with fields). |
| Polymorphism | Supports polymorphism (interface reference to implementing class). | Supports polymorphism (abstract class reference to subclass). |
| Example | java<br>interface Flyable {<br> void fly();<br>}<br> | java<br>abstract class Animal {<br> abstract void sound();<br> void sleep() { ... }<br>}<br> |
interface Drawable {
void draw();
default void describe() {
System.out.println("This is a drawable object.");
}
}
abstract class Shape {
String color;
Shape(String color) {
this.color = color;
}
abstract double getArea();
String getColor() {
return color;
}
}
class Circle extends Shape implements Drawable {
double radius;
Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
double getArea() {
return Math.PI * radius * radius;
}
@Override
public void draw() {
System.out.println("Drawing a " + getColor() + " circle.");
}
}
class Main {
public static void main(String[] args) {
Circle circle = new Circle("Red", 5);
circle.draw(); // Output: Drawing a Red circle.
circle.describe(); // Output: This is a drawable object.
System.out.println("Area: " + circle.getArea()); // Output: Area: 78.53981633974483
}
}
Common Concepts
Primitive Data types
java has 8 primitive data types
| Type | Size | Description | Default Value | Example Values |
|---|---|---|---|---|
byte | 1 byte | 8-bit signed integer | 0 | -128, 0, 127 |
short | 2 bytes | 16-bit signed integer | 0 | -32768, 0, 32767 |
int | 4 bytes | 32-bit signed integer | 0 | -2^31, 42, 2^31-1 |
long | 8 bytes | 64-bit signed integer | 0L | -2^63, 100L, 2^63-1 |
float | 4 bytes | 32-bit floating-point (IEEE 754) | 0.0f | 3.14f, -0.001f |
double | 8 bytes | 64-bit floating-point (IEEE 754) | 0.0d | 3.14159, -2.71828 |
char | 2 bytes | 16-bit Unicode character | ‘\u0000’ | ‘A’, ‘7’, ‘\n’ |
boolean | ~1 bit | True or false value | false | true, false |
Variables
a named storage space that hold value of a specific data type. rules -
- names are case sensitive
- start with a letter, _, $ and can include digits.
- local variables need explicit initializations, instance/static variables get default values.
Operators
operators are symbols that perform operations on variables and values.
arithmetic operators
| Operator | Description | Example |
|---|---|---|
+ | Addition | 5 + 3 = 8 |
- | Subtraction | 5 - 3 = 2 |
* | Multiplication | 5 * 3 = 15 |
/ | Division | 6 / 2 = 3 |
% | Modulus (remainder) | 7 % 3 = 1 |
unary operators
| Operator | Description | Example |
|---|---|---|
++ | Increment by 1 | x++ (x = x+1) |
-- | Decrement by 1 | x-- (x = x-1) |
+ | Unary plus | +5 |
- | Unary minus | -5 |
! | Logical NOT | !true = false |
relational operators
| Operator | Description | Example |
|---|---|---|
== | Equal to | 5 == 5 (true) |
!= | Not equal to | 5 != 3 (true) |
> | Greater than | 5 > 3 (true) |
< | Less than | 3 < 5 (true) |
>= | Greater than or equal | 5 >= 5 (true) |
<= | Less than or equal | 3 <= 5 (true) |
logical operators
| Operator | Description | Example | ||||
|---|---|---|---|---|---|---|
&& | Logical AND | true && false = false | ||||
|| | Logcal OR | true || false = true | ||||
! | Logical NOT | !true = false |
bitwise operators
| Operator | Description | Example | ||
|---|---|---|---|---|
& | Bitwise AND | 5 & 3 = 1 | ||
| | Bitwise OR | 5 | 3 = 7 | ||
^ | Bitwise XOR | 5 ^ 3 = 6 | ||
~ | Bitwise NOT | ~5 = -6 | ||
<< | Left shift | 5 << 1 = 10 | ||
>> | Right shift | 5 >> 1 = 2 | ||
>>> | Unsigned right shift | 5 >>> 1 = 2 |
assignment operators
| Operator | Description | Example |
|---|---|---|
= | Assign | x = 5 |
+= | Add and assign | x += 3 (x = x+3) |
-= | Subtract and assign | x -= 3 |
*= | Multiply and assign | x *= 3 |
/= | Divide and assign | x /= 3 |
%= | Modulus and assign | x %= 3 |
ternary operator
| Operator | Description | Example |
|---|---|---|
?: | Conditional expression | x > 0 ? "Positive" : "Non-positive" |
Instanceof operator
- Checks if an object is an instance of a class or interface.
- Example:
if (obj instanceof String) { ... }.
expressions
combination of variables, literals, operators and method call that evaluate to a single value. ex -3 + 5 , Math.sqrt(16).
statements
complete unit of execution in java. ends with ;
array
fixed sized order collection of elements of the same type. can be 1D, 2D and multi dimentional
int x = 10;
double y = 5.5;
boolean isPositive = x > 0;
double result = x * y + 3;
System.out.println("Result: " + result);
if (isPositive) {
System.out.println("x is positive");
}
int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
System.out.println("Number: " + numbers[i]);
}
Methods
- a method is a block of code within a class that defines a specific behavior or action an object can perform.
- it encapsulates functionality and can operate on an object’s data fields or external inputs.
- components -
- return type - specifies what a method return. ex -
void,int, etc. - method name - descriptive identifier (function name)
- parameters - optional inputs
- body - code that executes when method is called.
- return type - specifies what a method return. ex -
class Rectangle {
int length;
int width;
// Method to calculate area
int calculateArea() {
return length * width;
}
// Method to set dimensions
void setDimensions(int l, int w) {
length = l;
width = w;
}
}
public class Main {
public static void main(String[] args) {
Rectangle rect = new Rectangle();
rect.setDimensions(5, 3);
int area = rect.calculateArea(); // Returns 15
System.out.println("Area: " + area);
}
}
static methods
- a static method belongs to a class rather than an instance of the class.
- it can be called without creating an object.
- declared with
statickeyword
class MathUtils {
// Static method
static int square(int num) {
return num * num;
}
}
public class Main {
public static void main(String[] args) {
int result = MathUtils.square(4); // No object needed
System.out.println("Square: " + result); // Outputs: Square: 16
}
}
constructors
- it is a special method used to initialize objects when they are are created.
- it sets an initial state of an object’s field.
- has the same name as the class.
- no return type
- called automatically.
- typically public but can be private.
class Book {
String title;
int pages;
// Constructor
Book(String title, int pages) {
this.title = title; // this distinguishes field from parameter
this.pages = pages;
}
void display() {
System.out.println("Title: " + title + ", Pages: " + pages);
}
}
public class Main {
public static void main(String[] args) {
Book book = new Book("Java Basics", 300);
book.display(); // Outputs: Title: Java Basics, Pages: 300
}
}
overloading constructors
- constructor overloading allows a class to have multiple constructors with different parameter lists.
- constructors must differ in the number, type or order of parameters.
class Student {
String name;
int age;
String major;
// Default constructor
Student() {
this.name = "Unknown";
this.age = 18;
this.major = "Undeclared";
}
// Parameterized constructor
Student(String name, int age) {
this.name = name;
this.age = age;
this.major = "Undeclared";
}
// Another parameterized constructor
Student(String name, int age, String major) {
this.name = name;
this.age = age;
this.major = major;
}
// Using this() to call another constructor
Student(String name) {
this(name, 18); // Calls constructor with name and default age
}
void display() {
System.out.println("Name: " + name + ", Age: " + age + ", Major: " + major);
}
}
public class Main {
public static void main(String[] args) {
// Using different constructors
Student s1 = new Student(); // Default
Student s2 = new Student("Alice", 20);
Student s3 = new Student("Bob", 22, "Computer Science");
Student s4 = new Student("Charlie");
// Display results
s1.display(); // Outputs: Name: Unknown, Age: 18, Major: Undeclared
s2.display(); // Outputs: Name: Alice, Age: 20, Major: Undeclared
s3.display(); // Outputs: Name: Bob, Age: 22, Major: Computer Science
s4.display(); // Outputs: Name: Charlie, Age: 18, Major: Undeclared
}
}
Packages
- packages are mechanism for organizing classes and interfaces into namespace, preventing naming conflicts, improving code maintainability, and controlling access to classes.
- They group related code together, similar to folders in a file system.
defining a package
- a package is defined using the
packagekeyword at the beginning of a java source file. It specifies the namespace where the class resides.
// File: com/example/MyClass.java
package com.example;
public class MyClass {
public void display() {
System.out.println("Hello from MyClass!");
}
}
classpath
- the
classpathis an environment variable or command line parameter that tells the java compiler and jvm where to find the compiled.classfiles and libs. - purpose -
- helps locate classes in packages when compiling or running java programs.
- specifies directories, JAR files, or ZIP file containing class files.
package naming
- package names follow a hierarchical naming convention to avoid conflicts and ensure uniqueness.
- conventions -
- use lowercase letters.
- follow reverse domain name structure to ensure uniqueness.
- rules -
- must be valid identifiers (no spaces, special characters, or reserved keywords).
- should map to directory structure.
- avoid using java keywords or starting with numbers.
accessibility of packages
- packages control access to classes and their members using [[access modifiers]]. the accessibility of package members depends on these modifiers and package structure.
using package members
- to use classes, interfaces or other members from a package, you need to either import them or use their fully qualified name.
- using fully qualified name -
com.example.MyClass obj = new com.example.MyClass(); obj.display()
- using import - import a specific class or an entire packages to use its members without the fully qualified name.
import packageName.ClassName;
- static imports
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
public class Test {
public static void main(String[] args) {
System.out.println("PI: " + PI); // No Math.PI needed
System.out.println("Sqrt: " + sqrt(16)); // No Math.sqrt needed
}
}
Strings
Characters
- a character is a single 16-bit Unicode character, represented by the
charprimitive data type. - It can store any character from the Unicode character set (e.g., letters, digits, symbols).
char ch = 'A'; // Stores the character 'A' char unicodeChar = '\u0041'; // Unicode for 'A'
Strings
- A string is a sequence of characters.
- In Java, strings are objects of the
Stringclass (from thejava.langpackage), not primitive types. - Strings are immutable, meaning their content cannot be changed after creation.
String str = "Hello, World!";
string class
key features
-
immutability: ensures thread safety and security (e.g., in class loading or as keys in hash-based collections).
-
string pool: java maintains a pool of string literals to optimize memory. Strings created using literals (e.g.,
"Hello") are stored in the pool, while those created withnew String("Hello")are not (unless interned).String str1 = new String(); // Empty string String str2 = new String("Hello"); // String from literal char[] chars = {'H', 'i'}; String str3 = new String(chars); // String from char array -
string methods
length(): Returns the number of characters.charAt(int index): Returns the character at the specified index.substring(int beginIndex, int endIndex): Returns a substring.toUpperCase()/toLowerCase(): Converts case.indexOf(String str): Returns the index of the first occurrence ofstr.equals(Object obj)/equalsIgnoreCase(String str): Compares strings.trim(): Removes leading/trailing whitespace.replace(char oldChar, char newChar): Replaces characters.
valueO()
The String class provides valueOf() methods to convert various data types to strings. These are static methods.
int num = 42;
String strNum = String.valueOf(num); // "42"
double d = 3.14;
String strDouble = String.valueOf(d); // "3.14"
boolean b = true;
String strBool = String.valueOf(b); // "true"
char[] chars = {'H', 'i'};
String strChars = String.valueOf(chars); // "Hi"
StringBuffer Class
- The
StringBufferclass is used for mutable strings, unlike the immutableStringclass. It is thread-safe (synchronized methods), making it suitable for multi-threaded applications. - Key Features of StringBuffer hello
- Mutable: Allows in-place modification of the character sequence.
- Thread-safe: Synchronized methods ensure safe use in multi-threaded environments.
- Capacity: Internal buffer size, which grows dynamically (default initial capacity is 16).
StringBuffer sb1 = new StringBuffer(); // Empty, capacity 16
StringBuffer sb2 = new StringBuffer("Hello"); // Initialized with "Hello"
StringBuffer sb3 = new StringBuffer(50); // Empty, capacity 50
- StringBuffer Methods
append(String str): Appends data (overloaded for various types: int, double, etc.).insert(int offset, String str): Inserts data at the specified position.delete(int start, int end): Removes characters fromstarttoend-1.replace(int start, int end, String str): Replaces characters fromstarttoend-1withstr.reverse(): Reverses the character sequence.capacity(): Returns the current capacity.ensureCapacity(int minCapacity): Ensures the buffer has at least the specified capacity.toString(): Converts theStringBufferto aString.
When to Use StringBuffer
- Use
StringBufferfor string manipulation in multi-threaded environments. - For single-threaded applications, prefer
StringBuilderfor better performance (no synchronization overhead). - Example:
StringBuffer sb = new StringBuffer(); for (int i = 0; i < 1000; i++) { sb.append(i); // Efficient for repeated modifications }
String vs. StringBuffer
| Feature | String | StringBuffer |
|---|---|---|
| Mutability | Immutable | Mutable |
| Thread-Safety | Thread-safe (immutable) | Thread-safe (synchronized) |
| Performance | Slower for modifications | Faster for modifications |
| Memory Usage | Creates new objects | Modifies in-place |
| Use Case | Fixed strings | Dynamic string manipulation |
Exceptions
- exception is an event that disrupts the normal flow of a program during execution, typically caused by errors such as invalid input, file not found, or network issues.
- in java, exceptions are objects derived from the
java.lang.Throwableclass, which has two main subclasses. - java provides a structured way to handle exceptions using the
try-catchmechanism, with optionalfinallyandthrowconstructs.
handling exceptions
using try-catch
- the
catchblock executes only if the specified exception. - use
e.getMessage()to get the exception’s error messages ore.printStackTrace()for a detailed stack trace. - multiple catch can be used to catch multiple exceptions.
public class Main {
public static void main(String[] args) {
try {
int result = 10 / 0; // Throws ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Error: Division by zero");
System.out.println("Message: " + e.getMessage());
}
System.out.println("Program continues...");
}
}
using multiple catches
try {
String str = null;
System.out.println(str.length()); // Throws NullPointerException
int[] arr = new int[2];
arr[3] = 10; // Throws ArrayIndexOutOfBoundsException
} catch (NullPointerException e) {
System.out.println("Null reference: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Invalid index: " + e.getMessage());
}
catching superclass exceptions
try {
int result = 10 / 0;
} catch (Exception e) {
System.out.println("General error: " + e.getMessage());
}
using finally
the finally block contains code that executes regardless of whatever an exception is thrown or caught
public class Main {
public static void main(String[] args) {
try {
int result = 10 / 0; // Throws ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
} finally {
System.out.println("Cleanup in finally block");
}
}
}
types of exceptions
checked exceptions
- must be declared in a method’s
throwsclause or handled in atry-catchblock. - compile time checking ensures they are addressed.
- extend
java.lang.Exception - ex -
IOException,SQLException,classNotFoundException.
unchecked exception
- doesnt require explicit handling or declaration
- typically indicates programming errors or runtime issues.
- extend
java.lang.RuntimeException - ex -
NullPointerException,ArrayIndexOutOfBoundsException, etc.
throwing exceptions
the throw keyword is used to explicitly throw an expression either built in or custom exception.
public class Main {
public static void checkAge(int age) throws IllegalArgumentException {
if (age < 18) {
throw new IllegalArgumentException("Age must be 18 or older");
}
System.out.println("Access granted");
}
public static void main(String[] args) {
try {
checkAge(15);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
writing exception subclasses
// Custom checked exception
class InvalidBalanceException extends Exception {
public InvalidBalanceException(String message) {
super(message);
}
public InvalidBalanceException(String message, Throwable cause) {
super(message, cause);
}
}
// Custom unchecked exception
class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
class BankAccount {
private double balance;
public void withdraw(double amount) throws InvalidBalanceException {
if (amount < 0) {
throw new InvalidBalanceException("Withdrawal amount cannot be negative");
}
if (amount > balance) {
throw new InsufficientFundsException("Not enough funds");
}
balance -= amount;
}
public void setBalance(double balance) throws InvalidBalanceException {
if (balance < 0) {
throw new InvalidBalanceException("Balance cannot be negative");
}
this.balance = balance;
}
}
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount();
try {
account.setBalance(100);
account.withdraw(150); // Throws InsufficientFundsException
} catch (InvalidBalanceException e) {
System.out.println("Balance Error: " + e.getMessage());
} catch (InsufficientFundsException e) {
System.out.println("Funds Error: " + e.getMessage());
}
}
}
Multithreading
- multi-threading is an ability of a program to execute multiple threads concurrently, where each thread represents an independent flow of execution.
- threads share the same memory space within a process, making them lightweight compared to separate processes.
- benefits -
- concurrency - perform multiple tasks simultaneosly
- responsiveness - keep application responsive
- efficiency - utilizes cpu more effectively
- modularity - break complex tasks into independent threads.
- challenges -
- race conditions.
- deadlocks - thread waiting for each other infinitely.
- thread safety - ensuring shared resources are accessed correctly.
- thread lifecycle -
- new - thread created but not started
- runnable - thread is ready to run or running
- blocked/waiting - thread is waiting for a monitor lock or another condition.
- timed waiting - waiting for a specific amount of time.
- terminated - thread has completed execution.
main thread
- it is the default thread created when a java program starts.
- created automatically by jvm.
- responsible for executing
main()method - can create and manage other threads.
- program terminates when the main thread ends.
public class MainThread {
public static void main(String[] args) {
Thread current = Thread.currentThread(); // Get main thread
System.out.println("Main thread: " + current.getName());
System.out.println("Priority: " + current.getPriority());
}
}
java thread model
it provides a robust thread model through java.lang.Thread class and java.lang.Runnnable interface. threads can be created in two ways -
- extending the
threadclass
class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(getName() + ": Count " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // Start thread
}
}
- implementing
runnableinterface
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + ": Count " + i);
}
}
}
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(), "RunnableThread");
t1.start();
}
}
key methods
start(): Begins thread execution (calls run()).run(): Contains the thread’s task (override in subclass or Runnable).sleep(long millis): Pauses the thread for the specified time.join(): Waits for the thread to terminate.setName(String name): Sets the thread’s name.getName(): Gets the thread’s name.setPriority(int priority): Sets the thread’s priority.isAlive(): Checks if the thread is running.
thread priorities
thread priorities determine the relative importance of threads, influencing the schedular’s decision on which thread to execute when multiple threads are runnable. Java assigns prirorites as integer from 1 to 10.
Thread t1 = new Thread(() -> System.out.println("Low priority"));
t1.setPriority(Thread.MIN_PRIORITY);
Thread t2 = new Thread(() -> System.out.println("High priority"));
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
synchronization
- when multiple threads access the same shared resources, race conditions can occur, leading to inconsistent or incorrect results.
- Synchronization ensures that only one thread can access the data once at a time.
- mechanism -
- synchronization methods - add
synchronizedkeyword to a method to ensure only one thread can execute at a time for a given object. uses the object’s intrinsic lock (monitor). - synchronized blocks - synchronize a specific block of code using an object’s lock
- synchronization methods - add
// synchronization method
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
// synchronization blocks
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
interthread communication
- interthread communication allows threads to coordinate their actions, typically using the wait-notify mechanism to avoid busy-waiting (polling).
- this is useful when one thread needs to wait for another to complete a task or update a shared resource.
key methods
wait()- causes the current thread to wait until another thread callsnotify().notify()- wakes up one waiting threadnotifyall()- wakes up all waiting threads.
class SharedBuffer {
private int data;
private boolean available = false;
public synchronized void produce(int value) throws InterruptedException {
while (available) {
wait(); // Wait if buffer is full
}
data = value;
available = true;
System.out.println("Produced: " + data);
notifyAll();
}
public synchronized int consume() throws InterruptedException {
while (!available) {
wait(); // Wait if buffer is empty
}
available = false;
System.out.println("Consumed: " + data);
notifyAll();
return data;
}
}
public class Main {
public static void main(String[] args) {
SharedBuffer buffer = new SharedBuffer();
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
buffer.produce(i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
buffer.consume();
Thread.sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
Streams
java organizes I/O into byte and character streams, each with specific class for different tasks.
byte stream
- handle raw binary data
- base classes
InputStream- Abstract class for reading bytesOutputStream- Abstract class for writing bytes
- common subclasses -
FileInputStream,FileOutputStream- read/write bytes from/to files.BufferedInputStream,BufferedOutputStream- use buffering to reduce direct access to the underlying system.DataInputStream,DataOutputStream- read/write primitive data typesObjectInputStream,ObjectOutputStream- serialize/deserialize objects.
import java.io.*;
public class ByteStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
int byteData;
while ((byteData = fis.read()) != -1) { // Read byte-by-byte
fos.write(byteData); // Write byte
}
} catch (IOException e) {
System.out.println("IO Error: " + e.getMessage());
}
}
}
character streams
- handles unicode characters, ideal for text data.
- base classes -
Reader- abstract class for reading charactersWriter- abstract class for writing characters.
- common subclasses -
FileReader,FileWriter- read/write characters to/from files.BufferedReader,BufferedWriter- buffer characters data for efficiency.InputStreamReader,OutputStreamWriter- bridge bytes streams to character streams
import java.io.*;
public class CharStreamExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
System.out.println("IO Error: " + e.getMessage());
}
}
}
predefined streams
system.insystem.outsystem.err
I/O
java’s io is built around the concept of streams, which are a sequence of data used for input and output. Streams abstract the underlying data source or destinations, allowing uniform handling of different I/O operations.
concepts
- input - reading from data source
- output - writing data to a destination
- stream types -
- byte streams - handle raw binary data
- character streams - handle unicode characters
- IO operations are often wrapped in
try-catchwithIOException.
core packages
java.iojava.niojava.nio.file
Swing
- swing is a part of Java’s JFC(Java Foundation Classes) and provides a rich set of gui components for building desktop applications
- lightweight
- platform-independant
- highly customizable
Swing Components
- JFrame - top level container for a swing application
- JPanel - a generic container for grouping other containers
- JButton - a clickable button for user interactions
- JLabel - displays text or images
- JTextField - allows user input of a single line
- JTextArea - supports multi-line text input
- JCheckBox - a textbox for selecting options
- JRadioButton - a radio button for mutually exclusive selections
- JComboBox - a dropdown menu for selecting one option from a list
- JTable - displays tabular data
- JScrollPane - adds scrollbar to components like textarea or tables
- JMenu - for creating menu’s
- JMenuBar - for modal or non-modal dialog boxes.
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Swing App");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
JPanel panel = new JPanel()
JLabel label = new JLabel("Enter Name: ");
JTextField textField = new JTextField(20);
JButton button = new JButton("Submit");
panel.add(label);
panel.add(textField);
panel.add(button);
frame.add(panel);
frame.setVisible(true);
}
}
Look and feel
the look and feel defines the appearance and behavior of swing components. Swing components supports pluggable look and feel, allowing you to changethe visual style of the application.
common look and feel options are -
- metal
- nimbus
- windows
- motif
- system look and feel
public class Main {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch(Exception e) {
e.printStackTrace();
}
JFrame frame = new JFrame("Nimbus L&F Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
JPanel panel = new JPanel();
panel.add(new JButton("Click Me"));
panel.add(new JLabel("Nimbus Look & Feel"));
frame.add(panel);
frame.setVisible(true);
}
}
Event listeners
swing uses an event-driver model where components generate events and event listeners handle them.
common event listeners -
- ActionListener - Handles actions like button clicks
- MouseListener - Responds to mouse events
- KeyListener - Handles keyboard input
- WindowListener - Manages window events
- ItenListener - handles changes in components like checkboxes or combo boxes.
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
JPanel panel = new JPanel();
JButton button = new JButton("Click Me");
JLabel label = new JLabel("No clicks yet");
button.addActionListener(e -> label.setText("Button Clicked!"));
panel.add(button);
panel.add(label);
frame.add(panel);
frame.setVisible(true);
}
}
Concurrency in Swing
- swing is single threaded and all gui updates must occur on the event dispatch thread(EDT) to avoid thread safety issues.
- Long running tasks are offloaded to worker threads to prevent GUI from freezing
import javax.swing.*;
import java.awt.*;
import java.util.List;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("SwingWorker Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
JPanel panel = new JPanel();
JLabel label = new JLabel("Status: Idle");
JButton startButton = new JButton("Start Task");
startButton.addActionListener(e -> {
SwingWorker<Void, String> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
for (int i = 1; i <= 5; i++) {
Thread.sleep(1000); // Simulate long task
publish("Processing step " + i);
}
return null;
}
@Override
protected void process(List<String> chunks) {
label.setText(chunks.get(chunks.size() - 1));
}
@Override
protected void done() {
label.setText("Task Completed!");
}
};
worker.execute();
});
panel.add(startButton);
panel.add(label);
frame.add(panel);
frame.setVisible(true);
}
}
Keywords
final
final keyword is used to provide restriction to classes, methods or variables, ensuring immutability.
uses
- variables - make then constant
- -methods - cannot be overridden
- classes - cannot be inherited.
class Example {
final int MAX_VALUE = 100; // Final instance variable, initialized at declaration
final double PI; // Blank final variable
Example() {
PI = 3.14159; // Initialized in constructor
}
void modify() {
// MAX_VALUE = 200; // Compilation error: Cannot assign a value to final variable
// PI = 3.14; // Compilation error
}
}
class Main {
public static void main(String[] args) {
final int localVar = 10; // Final local variable
// localVar = 20; // Compilation error
System.out.println(localVar); // Output: 10
Example ex = new Example();
System.out.println(ex.MAX_VALUE); // Output: 100
System.out.println(ex.PI); // Output: 3.14159
}
}
- final prevents reassigning the reference but the objects internal state can still be modified.
super
the super keyword is used in subclasses to
- call the superclass constructors
- access superclass members
class Animal {
String name = "Animal";
void eat() {
System.out.println("Animal eats.");
}
}
class Dog extends Animal {
String name = "Dog";
void eat() {
super.eat(); // Call superclass method
System.out.println("Dog eats bones.");
}
void display() {
System.out.println("Superclass name: " + super.name); // Access superclass field
}
}
class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // Output: Animal eats. Dog eats bones.
dog.display(); // Output: Superclass name: Animal
}
}
this
this keyword is a reference to the current object of the class. it is used to resolve ambiguity between instance variables and parameters or to invoke methods/constructors of the current class.
referring to instance variable
class Person Person {
String name;
int age;
Person(String name, int age) {
this.name = name; // 'this.name' refers to the instance variable, 'name' to the parameter
this.age = age;
}
}
invoking another constructor
class Person {
String name;
int age;
Person(String name) {
this(name, 0); // Calls the constructor with two parameters
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
returning to current object
class Person {
String name;
int age;
Person setName(String name) {
this.name = name;
return this; // Returns the current object
}
Person setAge(int age) {
this.age = age;
return this;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Alice").setAge(25); // Method chaining
}
}
passing the current object to a method
class Person {
String name;
void display(Person person) {
System.out.println("Person's name: " + person.name);
}
void show() {
display(this); // Passes the current object
}
}
- usage of
thiskeyword is not allowed in static blocks or methods because they belong to the class rather than the instance. - improved code readability and prevent errors due to naming conflicts
- it is commonly used in constructors, getters and setters.
finalize
- the
finalize()method is called by the garbage collector before an object is reclaimed. It’s defined in theObjectclass and can be overriden to perform cleanup protected void finalize() throws Throwable- rarely used in modern java as try catch is more preferrable
- deprecated in java 9 due to performance issues and unreliability.
class Resource {
@Override
protected void finalize() throws Throwable {
System.out.println("Cleaning up resource");
// Perform cleanup
super.finalize();
}
}
class Main {
public static void main(String[] args) {
Resource r = new Resource();
r = null; // Eligible for GC
System.gc(); // Suggests GC, may trigger finalize()
}
}
Transient and Volatile
serialization
it is the process of converting an object’s state into a byte stream, which can be saved to a file, sent over a network or stored in a database.
transient modifier
- The
transientmodifier is used to indicate that a field should not be serialized when an object is converted to a byte stream - When an object is serialized, transient fields are excluded from the serialized data and are typically initialized to their default values (e.g., null for objects, 0 for numbers) when deserialized.
import java.io.*;
class User implements Serializable {
String name;
transient String password; // This field won't be serialized
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
public class Main {
public static void main(String[] args) throws Exception {
User user = new User("Alice", "secret123");
// Serialize
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
oos.writeObject(user);
oos.close();
// Deserialize
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User deserializedUser = (User) ois.readObject();
ois.close();
System.out.println("Name: " + deserializedUser.name); // Output: Alice
System.out.println("Password: " + deserializedUser.password); // Output: null
}
}
volatile
- The volatile modifier ensures that a field’s value is always read from and written to the main memory, preventing thread-local caching.
- It guarantees visibility of changes to a variable across multiple threads and prevents certain compiler optimizations that could reorder instructions.
- Ensures visibility: When one thread modifies a volatile variable, the change is immediately visible to all other threads.
- Prevents reordering: The compiler and JVM won’t reorder operations on a volatile variable in a way that breaks its visibility guarantees.
- Does not provide atomicity (e.g., i++ is not thread-safe even if i is volatile).
class SharedResource {
volatile boolean flag = false;
public void toggleFlag() {
flag = true; // Write to main memory
}
public boolean isFlag() {
return flag; // Read from main memory
}
}
public class Main {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
// Thread 1: Modifies flag
new Thread(() -> {
try {
Thread.sleep(1000);
resource.toggleFlag();
System.out.println("Flag set to true");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// Thread 2: Reads flag
new Thread(() -> {
while (!resource.isFlag()) {
// Busy wait
}
System.out.println("Flag is now true");
}).start();
}
}
| Feature | transient | volatile |
|---|---|---|
| Purpose | Controls serialization | Ensures thread visibility |
| Applied To | Instance fields | Instance or static fields |
| Context | Serialization (I/O) | Multithreading |
| Effect | Excludes field from serialization | Ensures reads/writes go to main memory |
| Thread Safety | Not related to threads | Provides visibility, not atomicity |
Garbage Collection
- java’s garbage collector automatically reclaims memory by deallocating objects that are no longer reachable.
- this is managed by jvm
- garbage collection primarily works on the heap.
concepts
- heap memory - java objects are stored in the heap, a region of memory divided into areas like:
- young generation - new objects are allocated here.
- old generation - for long lived objects
- meta-space - for class metadata.
- reachability - an object is reachable if it is accessible by the chain of references from root. if it is not it is elidgible for garbage collection.
working
- marking - the gc traverse the object graphing, marking all objects that are being referenced . unmarked objects are considered for garbage collection
- sweeping - the gc reclaims the memory by freeing the objects or moving the objects.
- compaction - some gc compact the heap by moving live objects together reducing fragmentation and improving memory allocation frequency.
advantages
- prevents memory leaks by automatically freeing unused memory
- eleminates manual memory management like in c/c++.
- reduces bugs like dangling pointer and double -free errors.
disadvantages
- unpredictable timing
- performance overhead
- limited control
class Main {
public static void main(String[] args) {
Box b1 = new Box(); // b1 references a Box object
Box b2 = new Box(); // b2 references another Box object
b1 = null; // b1's object is now eligible for GC
b2 = b1; // b2's original object is also eligible for GC
System.gc(); // Suggest GC
}
}
Native Methods
- native methods in Java are methods declared in a Java class but implemented in a native language like C or C++ using the Java Native Interface (JNI).
- They are used when Java code needs to interact with hardware, operating system, or performance-critical operations that Java cannot handle efficiently.
- The native keyword is used to declare such methods, and they are typically loaded from a shared library (e.g., .dll on Windows or .so on Unix).
concepts
- Native Method Declaration - use the native keyword in the method signature, with no implementation in Java.
- JNI: The Java Native Interface provides the framework to call native code and pass data between Java and the native environment.
- Shared Library: The native code is compiled into a shared library, which is loaded into the JVM using
System.loadLibrary(). - Instance Context: For instance native methods, the native code receives a reference to the Java object (
this) via theJNIEnvpointer.