The HDFS Java API

The shell is great for people; programs need an API. This lesson writes and runs two Java programs against the lab cluster with the Hadoop FileSystem API: one that writes, reads, inspects, renames and deletes a file, and one that counts items in a big file. Then the same file is read over plain HTTP with WebHDFS. Every output is real.

Module 6 · HDFS operations⏱ 26 minIntermediateReal Java programsWebHDFS with curlRuns in your browser (Python)8 practice problems
1 The story

When the shell is not enough

The canteen app's backend, written in Java, must save every order batch into HDFS and read daily totals back. Riya's first version calls hdfs dfs -put from Java with Runtime.exec(). It works, but each call starts a new JVM: 1.8 seconds per batch, and errors arrive as text she has to parse. Arjun shows her that every hdfs dfs command is itself a small Java program built on one class, FileSystem, and her backend can use that class directly.

your Java app hdfs dfs (FsShell) MapReduce, Spark, Hive FileSystem API create open appendlistStatus renamedelete getFileStatusgetFileBlockLocations NameNode DataNodes hdfs:// file:// s3a:// abfs:// same code, other storage metadata calls go to the NameNode; bytes stream to and from DataNodes myphatshala.in
Arjun Sir

Everything you did with the shell, you can do from code: fs.create() is -put, fs.open() is -cat, fs.rename() is -mv. The JVM starts once, connections are reused, and errors are real Java exceptions.

Riya

Is it different from writing a normal file?

Arjun Sir

Almost the same streams you know from Java. The difference is what happens underneath: a create asks the NameNode, the bytes go through the write pipeline from Lesson 6.1, and open reads from the nearest DataNode as in Lesson 6.2. Spark and Hive use this same class, which is why they can read HDFS and S3 with the same code.

2 Definition

The Hadoop FileSystem API

Formal definition (write this in exams)

The Hadoop FileSystem API (org.apache.hadoop.fs) is the Java interface through which all Hadoop clients access storage. FileSystem.get(Configuration) returns an implementation chosen by the URI scheme of fs.defaultFS or of a Path; for hdfs:// it is DistributedFileSystem, which uses a DFSClient to call the NameNode over RPC and to stream data to and from DataNodes. Files are created with create() (returning an FSDataOutputStream that supports hflush() and hsync()) and read with open() (returning a seekable FSDataInputStream). Metadata is returned as FileStatus and block placement as BlockLocation; namespace operations include mkdirs, listStatus, rename and delete.

In simple words

A Java library that lets your program open, write, list, rename and delete HDFS files, the way the shell does, but inside your own application.

Words you'll meet in this lesson

ConfigurationLoads core-site.xml and hdfs-site.xml from the classpath; fs.defaultFS says where HDFS is.
FileSystemThe abstract class; get one with FileSystem.get(conf).
PathA file or folder name, absolute or relative, optionally a full URI.
FSDataOutputStreamWhat create() returns; hflush() makes data visible to readers.
FSDataInputStreamWhat open() returns; supports seek(offset).
FileStatusLength, replication, block size, owner, permission, times.
BlockLocationOffset, length and hosts of one block.
WebHDFSThe same operations over HTTP/JSON, for non-Java programs.

API facts worth remembering

FileSystem.get(conf)Returns a cached, shared instance per scheme, authority and user; closing it closes it for the whole JVM
rename()Returns false on many failures instead of throwing: always check it
delete(path, true)Recursive and permanent: the API does not use the trash
create(path, true)true = overwrite an existing file
VisibilityOther readers see written data after hflush() or close()
Classpathhadoop classpath prints the jars to compile and run with

The API at three levels

Pick your level. New to this? Start at Beginner and climb.

Beginner Like Java file handling you already know

  • Path is like java.io.File, open()/create() give streams.
  • You wrap them in a BufferedReader or write bytes, as usual.
  • Always close streams (try-with-resources).

Intermediate Everyday use

  • Upload: fs.copyFromLocalFile(src, dst), or create() and write.
  • Check before reading: fs.exists(p), fs.getFileStatus(p).getLen().
  • Walk a tree: fs.listFiles(dir, true) (recursive iterator, includes block locations).
  • Publish results: write into a temporary folder, then one fs.rename().

Advanced What library and framework authors care about

  • getFileBlockLocations is how MapReduce and Spark place tasks near data.
  • hflush() vs hsync(): visible to readers vs also forced to disk on DataNodes.
  • Seek + read lets a reader start in the middle of a file: that is how input splits work.
  • Use FileSystem.newInstance() if your code must close its own instance without affecting others.
  • On a secure cluster, the same code needs a Kerberos login (UserGroupInformation).

Exam tip: “Write a Java program to read a file from HDFS.” Steps: create a Configuration; FileSystem fs = FileSystem.get(conf); FSDataInputStream in = fs.open(new Path(...)); read lines with a BufferedReader (or IOUtils.copyBytes(in, System.out, 4096, false)); close the stream. Mention that open() gets block locations from the NameNode and reads from DataNodes.

3 Types and ways to use it

API methods, shell commands and what happens underneath

FileSystem methodShell equivalentTalks to
mkdirs(p)-mkdir -pNameNode
create(p, overwrite) + write + close-putNameNode, then DataNode pipeline
append(p)-appendToFileNameNode, then DataNodes
open(p) + read-cat, -getNameNode (locations), then DataNodes
getFileStatus(p)-stat, -ls one fileNameNode
listStatus(dir), listFiles(dir, recursive)-ls, -ls -RNameNode
getFileBlockLocations(st, 0, len)fsck -blocks -locationsNameNode
rename(src, dst)-mvNameNode
delete(p, recursive)-rm -r -skipTrashNameNode (blocks freed later)
setReplication(p, n)-setrepNameNode

Ways to reach HDFS from a program

Java FileSystem API

Native and fastest; what Hadoop's own tools use. This lesson.

Java, Scala

WebHDFS (REST)

HTTP + JSON on the NameNode port 9870, redirecting to DataNodes on 9864. Any language, even curl.

Any language

libhdfs / pyarrow

A C library over the Java client; Python's pyarrow.fs.HadoopFileSystem uses it.

C, Python

Through a framework

Spark, Hive, Flink read hdfs:// paths with this API inside; you rarely see it.

Most common

When you change hdfs:// to s3a://bucket/ in a path, the same code talks to cloud storage, if the connector jar and credentials are configured. That portability is the main reason the API is an abstract FileSystem class.

4 Step by step

Two real programs on the lab cluster

Both Java files are in “Run it on your PC” below. We compiled and ran them inside the namenode container, where Java and the Hadoop jars are already installed.

  1. Compile and package

    javac -cp $(hadoop classpath) HdfsBasics.java ItemCount.java
    jar cf hdfs-demo.jar *.class
    hadoop jar hdfs-demo.jar HdfsBasics
    Connected to: hdfs://namenode
    hadoop classpath lists every Hadoop jar. hadoop jar runs the class with that classpath and the cluster's configuration, so new Configuration() finds fs.defaultFS=hdfs://namenode and FileSystem.get() returns a DistributedFileSystem.
  2. Write and read back

    try (FSDataOutputStream out = fs.create(file, true)) {
        out.write("order_id,item,amount\n".getBytes(StandardCharsets.UTF_8));
        out.write("3001,samosa,18\n3002,chai,15\n".getBytes(StandardCharsets.UTF_8));
    }
    try (FSDataInputStream in = fs.open(file); BufferedReader r = ...) {
        while ((line = r.readLine()) != null) System.out.println("  read: " + line);
    }
    read: order_id,item,amount read: 3001,samosa,18 read: 3002,chai,15
    create = NameNode create + addBlock; the bytes went through a 3-DataNode pipeline; closing the stream completed the file. open fetched block locations and streamed from a DataNode.
  3. Metadata and block locations

    FileStatus st = fs.getFileStatus(file);
    for (BlockLocation b : fs.getFileBlockLocations(bs, 0, bs.getLen())) ...
    orders.csv 49 bytes replication 3 block size 128 MB owner hadoop block at offset 0 length 134,217,728 on ab3b1bedf98a, 46201bb62e68, 264eb9c0f8e2 block at offset 134,217,728 length 6,542,542 on ab3b1bedf98a, 46201bb62e68, 264eb9c0f8e2
    The hosts are Docker container IDs: datanode-1, datanode-2 and datanode-3. orders_x30.csv (140,760,270 bytes) has a full 128 MB block plus a 6.5 MB tail block. A job scheduler uses exactly this list to run tasks next to the data.
  4. List, rename, delete

    System.out.println("rename: " + fs.rename(file, renamed));
    System.out.println("exists after rename: " + fs.exists(file));
    System.out.println("delete: " + fs.delete(dir, true));
    ls: hdfs://namenode/user/hadoop/api_demo/orders.csv rename: true exists after rename: false delete: true
    Both return booleans. A rename to a missing parent folder returns false without an exception, so check it. delete(dir, true) removed the folder and the file permanently: the API skips the trash.
  5. Count items in one file

    hadoop jar hdfs-demo.jar ItemCount /user/hadoop/data/canteen_orders.csv
    chai 50,170 dosa 49,948 idli 49,984 poha 49,891 samosa 50,021 vada pav 49,986 took 1327 ms in one JVM
    300,000 orders (4.5 MB) streamed through one client in 1.3 seconds, including connecting. The program reads the file block after block like -cat does.
  6. Same program, 90× the data, and a bug

    hadoop jar hdfs-demo.jar ItemCount /user/hadoop/data/orders_x90.csv
    chai 4,515,300 dosa 4,495,320 idli 4,498,560 item 89 poha 4,490,190 samosa 4,501,890 vada pav 4,498,740 took 5890 ms in one JVM
    403 MB took 5.9 s: about 68 MB/s through one client. A 4 TB file would take about 17 hours this way, which is why MapReduce (Module 8) runs one reader per block in parallel. And “item 89”: orders_x90.csv was made by concatenating the file 90 times, so 89 header lines sit inside the data. HDFS stores bytes; cleaning data is the program's job.
  7. The same file over HTTP: WebHDFS

    curl -s "http://namenode:9870/webhdfs/v1/user/hadoop/canteen/raw/day1.csv?op=GETFILESTATUS&user.name=hadoop"
    curl -s -i "http://namenode:9870/webhdfs/v1/user/hadoop/canteen/raw/day1.csv?op=OPEN&user.name=hadoop" | grep -iE "^HTTP|^Location"
    {"FileStatus":{"accessTime":1792579946689,"blockSize":134217728,"childrenNum":0,"fileId":16425,"group":"supergroup","length":47,"modificationTime":1792575484677,"owner":"hadoop","pathSuffix":"","permission":"640","replication":3,"storagePolicy":0,"type":"FILE"}} HTTP/1.1 307 Temporary Redirect Location: http://46201bb62e68:9864/webhdfs/v1/user/hadoop/canteen/raw/day1.csv?op=OPEN&user.name=hadoop&namenoderpcaddress=namenode:8020&offset=0
    The NameNode answers metadata itself but never sends file data: for OPEN it redirects (HTTP 307) to a DataNode (datanode-2, port 9864). The same split as the Java client, in plain HTTP.

Don't call fs.close() on the instance from FileSystem.get() in a long-running server: it is shared, and other parts of the application will start failing with “Filesystem closed”. Close streams, keep the FileSystem open (or use FileSystem.newInstance()). Our short demo programs close it on exit, which is fine.

5 See it happen

Six API calls, and who answers each

The calls from HdfsBasics, in order.

From Java call to NameNode and DataNodes 6 steps
What you see HdfsBasics.java fs = FileSystem.get(conf);Connected to: hdfs://namenode out = fs.create(file, true);(no output) out.write(...); out.close();(no output: 49 bytes stored) in = fs.open(file);(no output yet) r.readLine() ...read: order_id,item,amountread: 3001,samosa,18read: 3002,chai,15 fs.rename(...); fs.delete(dir, true);rename: truedelete: true myphatshala.in
Behind the screen your JVM FileSystem = Distributed- FileSystem DFSClient NameNode :8020 dn1 dn2 dn3 fs.defaultFS = hdfs://namenode → DistributedFileSystem createRPC create: a new, empty file in the namespace addBlock, pipeline dn1→dn2→dn3, then complete getBlockLocationsNameNode returns sorted replica locations bytes stream from one DataNode, checksums verified rename, deletemetadata only; blocks deleted later by DataNodes myphatshala.in
6 Real-world use

The API at work

ingest servicehflush()

1. Ingestion services

Services that collect events (from Kafka, logs or apps) keep an HDFS file open, write records and call hflush() regularly so readers see recent data, then roll to a new file every hour.

Events→create + write + hflush→Hourly files
hdfs:// → s3a://same code

2. Moving to the cloud

Teams migrating from on-premise Hadoop to cloud object stores often change only paths and configuration: Spark and Hive jobs keep working because they talk to the FileSystem abstraction.

hdfs:// paths→FileSystem API→s3a:// paths
WebHDFSPython, dashboards

3. Non-Java tools

Dashboards and Python scripts read result files through WebHDFS or HttpFS (a gateway that also hides the DataNodes), with no Hadoop installation on the client.

Python app→HTTP/JSON→HDFS
small-file reportlistFiles(…, true)

4. Housekeeping tools

Admins write small Java tools that walk the namespace once with listFiles(dir, true) to find small files, old data or wrong replication, far faster than thousands of shell calls.

listFiles→Filter in Java→Report or fix

Simplified; organisations differ in tools and policies.

7 Run the code

The FileSystem API, modelled in Python

The Java API can't run in your browser, so this Python class copies its shape: create, open, get_file_status, get_file_block_locations, rename and delete, with a NameNode namespace and 128 MB blocks placed on DataNodes. Run it, then try the tasks.

BLOCK = 128 * 1024 * 1024

class FileSystem:
    def __init__(self, datanodes):
        self.ns = {}                       # path -> (length, replication, blocks)
        self.dns = datanodes
        self.next_dn = 0

    def create(self, path, data_len, replication=3):
        blocks = []
        for off in range(0, data_len, BLOCK):
            hosts = [self.dns[(self.next_dn + i) % len(self.dns)] for i in range(replication)]
            self.next_dn += 1
            blocks.append((off, min(BLOCK, data_len - off), hosts))
        self.ns[path] = (data_len, replication, blocks)

    def get_file_status(self, path):
        length, rep, blocks = self.ns[path]
        return {"path": path, "len": length, "replication": rep, "blocksize": BLOCK}

    def get_file_block_locations(self, path):
        return self.ns[path][2]

    def rename(self, src, dst):            # returns False instead of raising, like Java
        if src not in self.ns or dst in self.ns:
            return False
        self.ns[dst] = self.ns.pop(src)
        return True

    def delete(self, path):
        return self.ns.pop(path, None) is not None

fs = FileSystem(["datanode-1", "datanode-2", "datanode-3"])
fs.create("/user/hadoop/data/orders_x30.csv", 140_760_270)
print(fs.get_file_status("/user/hadoop/data/orders_x30.csv"))
for off, length, hosts in fs.get_file_block_locations("/user/hadoop/data/orders_x30.csv"):
    print(f"  block at offset {off:,} length {length:,} on {', '.join(hosts)}")
print("rename:", fs.rename("/user/hadoop/data/orders_x30.csv", "/user/hadoop/data/old.csv"))
print("rename again:", fs.rename("/user/hadoop/data/orders_x30.csv", "/user/hadoop/data/old.csv"))
{'path': '/user/hadoop/data/orders_x30.csv', 'len': 140760270, 'replication': 3, 'blocksize': 134217728} block at offset 0 length 134,217,728 on datanode-1, datanode-2, datanode-3 block at offset 134,217,728 length 6,542,542 on datanode-2, datanode-3, datanode-1 rename: True rename again: False
  • for off in range(0, data_len, BLOCK):The file is cut into 128 MB blocks; the last one is shorter. The real file gave 134,217,728 + 6,542,542 bytes.
  • def rename(self, src, dst): # returns False instead of raising, like JavaJava's fs.rename() also returns a boolean. The real program printed rename: true. Code that ignores a false silently loses track of files.
  • def get_file_block_locations(self, path):Only metadata: the NameNode knows the hosts of every block. Schedulers use this to run tasks next to the data.

Run it on your PC: Java against HDFS

Save these files in the hdfs-lab folder. The namenode container already has Java and the Hadoop jars, so we copy the files in, compile there with javac -cp $(hadoop classpath), package a jar and run it with hadoop jar. The exact commands are at the end of the first file.

Write, read, inspect, rename, delete

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class HdfsBasics {
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();          // reads core-site.xml, hdfs-site.xml
        try (FileSystem fs = FileSystem.get(conf)) {
            System.out.println("Connected to: " + fs.getUri());

            Path dir = new Path("/user/hadoop/api_demo");
            fs.mkdirs(dir);

            // write a small file
            Path file = new Path(dir, "orders.csv");
            try (FSDataOutputStream out = fs.create(file, true)) {   // true = overwrite
                out.write("order_id,item,amount\n".getBytes(StandardCharsets.UTF_8));
                out.write("3001,samosa,18\n3002,chai,15\n".getBytes(StandardCharsets.UTF_8));
            }

            // read it back
            try (FSDataInputStream in = fs.open(file);
                 BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
                String line;
                while ((line = r.readLine()) != null) {
                    System.out.println("  read: " + line);
                }
            }

            // metadata, as the NameNode stores it
            FileStatus st = fs.getFileStatus(file);
            System.out.printf("%s  %d bytes  replication %d  block size %d MB  owner %s%n",
                    st.getPath().getName(), st.getLen(), st.getReplication(),
                    st.getBlockSize() / (1024 * 1024), st.getOwner());

            // where are the blocks of a big file?
            Path big = new Path("/user/hadoop/data/orders_x30.csv");
            FileStatus bs = fs.getFileStatus(big);
            for (BlockLocation b : fs.getFileBlockLocations(bs, 0, bs.getLen())) {
                System.out.printf("  block at offset %,d length %,d on %s%n",
                        b.getOffset(), b.getLength(), String.join(", ", b.getHosts()));
            }

            // list, rename, delete
            for (FileStatus s : fs.listStatus(dir)) {
                System.out.println("  ls: " + s.getPath());
            }
            Path renamed = new Path(dir, "orders_old.csv");
            System.out.println("rename: " + fs.rename(file, renamed));
            System.out.println("exists after rename: " + fs.exists(file));
            System.out.println("delete: " + fs.delete(dir, true));             // true = recursive
        }
    }
}

// In the hdfs-lab folder:
//   docker cp HdfsBasics.java lab5-namenode-1:/tmp/
//   docker cp ItemCount.java lab5-namenode-1:/tmp/
//   docker exec -it lab5-namenode-1 bash
// Then inside the container:
//   cd /tmp
//   javac -cp $(hadoop classpath) HdfsBasics.java ItemCount.java
//   jar cf hdfs-demo.jar *.class
//   hadoop jar hdfs-demo.jar HdfsBasics
Connected to: hdfs://namenode read: order_id,item,amount read: 3001,samosa,18 read: 3002,chai,15 orders.csv 49 bytes replication 3 block size 128 MB owner hadoop block at offset 0 length 134,217,728 on ab3b1bedf98a, 46201bb62e68, 264eb9c0f8e2 block at offset 134,217,728 length 6,542,542 on ab3b1bedf98a, 46201bb62e68, 264eb9c0f8e2 ls: hdfs://namenode/user/hadoop/api_demo/orders.csv rename: true exists after rename: false delete: true

Real output. The hosts are the container IDs of datanode-1, -2 and -3 (yours will differ). orders_x30.csv is canteen_orders.csv repeated 30 times; if you don't have it: for i in $(seq 30); do cat canteen_orders.csv; done | hdfs dfs -put - /user/hadoop/data/orders_x30.csv

Count orders per item, streaming one file

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

/** Count orders per item by streaming one HDFS file through a single client. */
public class ItemCount {
    public static void main(String[] args) throws Exception {
        Path path = new Path(args[0]);
        Map<String, Integer> counts = new TreeMap<>();
        long start = System.currentTimeMillis();
        try (FileSystem fs = FileSystem.get(new Configuration());
             FSDataInputStream in = fs.open(path);
             BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
            r.readLine();                                         // skip the header
            String line;
            while ((line = r.readLine()) != null) {
                String item = line.split(",")[1];
                counts.merge(item, 1, Integer::sum);
            }
        }
        counts.forEach((item, n) -> System.out.printf("%-9s %,d%n", item, n));
        System.out.println("took " + (System.currentTimeMillis() - start) + " ms in one JVM");
    }
}

// Inside the namenode container, in /tmp:
//   hadoop jar hdfs-demo.jar ItemCount /user/hadoop/data/canteen_orders.csv
//   hadoop jar hdfs-demo.jar ItemCount /user/hadoop/data/orders_x90.csv
chai 50,170 dosa 49,948 idli 49,984 poha 49,891 samosa 50,021 vada pav 49,986 took 1327 ms in one JVM chai 4,515,300 dosa 4,495,320 idli 4,498,560 item 89 poha 4,490,190 samosa 4,501,890 vada pav 4,498,740 took 5890 ms in one JVM

Real output for the 4.5 MB and the 403 MB file. “item 89” comes from header lines inside the concatenated file: a data bug, not an HDFS one.

The same file over HTTP (WebHDFS)

#!/usr/bin/env bash
# webhdfs.sh: the same file system over plain HTTP (REST). No Java, no Hadoop client needed.
# Run inside the namenode container:  bash webhdfs.sh
NN=http://namenode:9870/webhdfs/v1
F=/user/hadoop/canteen/raw/day1.csv
echo "--- GETFILESTATUS (metadata, answered by the NameNode)"
curl -s "$NN$F?op=GETFILESTATUS&user.name=hadoop"; echo
echo "--- OPEN without following redirects"
curl -s -i "$NN$F?op=OPEN&user.name=hadoop" | grep -iE "^HTTP|^Location"
echo "--- OPEN, following the redirect to the DataNode (-L)"
curl -s -L "$NN$F?op=OPEN&user.name=hadoop"

# In the hdfs-lab folder:
#   docker cp webhdfs.sh lab5-namenode-1:/tmp/
#   docker exec lab5-namenode-1 bash /tmp/webhdfs.sh
--- GETFILESTATUS (metadata, answered by the NameNode) {"FileStatus":{"accessTime":1792579946689,"blockSize":134217728,"childrenNum":0,"fileId":16425,"group":"supergroup","length":47,"modificationTime":1792575484677,"owner":"hadoop","pathSuffix":"","permission":"640","replication":3,"storagePolicy":0,"type":"FILE"}} --- OPEN without following redirects HTTP/1.1 307 Temporary Redirect Location: http://46201bb62e68:9864/webhdfs/v1/user/hadoop/canteen/raw/day1.csv?op=OPEN&user.name=hadoop&namenoderpcaddress=namenode:8020&offset=0 --- OPEN, following the redirect to the DataNode (-L) order_id,item,amount 2001,chai,15 2002,dosa,45

Real output. The NameNode answers GETFILESTATUS itself and redirects OPEN (HTTP 307) to a DataNode on port 9864.

8 Line by line

Reading a file block by block, as open() does

A simplified reader: ask for block locations once, then read each block from its first (nearest) host until the file's length is reached.

Worked examples: real problems, solved and traced in memory

Three API questions with numbers: what one JVM saves over shell calls, how fast one client can scan, and how many readers you need. Each runs live and is traced in memory.

Example 1BeginnerAPI · JVM start-up

API instead of shell calls

Problem statement

A backend saves 2,000 small order batches a day. Through hdfs dfs -put each call costs about 1.8 s of JVM start-up; through the API in a running service, about 0.02 s each. How much time is saved per day?

Input
2,000 calls · 1.8 s vs 0.02 s
Expected output
shell 60 min, API 40 s, saved 59 min per day

Approach

  1. Shell total = calls × 1.8 s.
  2. API total = calls × 0.02 s.
  3. Difference in minutes.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 2IntermediateThroughput · one client

How long does one reader take?

Problem statement

ItemCount read 403 MB in 5.89 s. At that speed, how long would one client take for 1 TB, and for 4 TB?

Input
402.7 MB in 5.89 s
Expected output
1 TB: 4.3 hours at 68 MB/s
4 TB: 17.0 hours at 68 MB/s

Approach

  1. Speed = MB ÷ seconds.
  2. Time = size ÷ speed.
  3. Convert to hours.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 3AdvancedParallelism · blocks

How many parallel readers for a 1-hour target?

Problem statement

A 4 TB file has 128 MB blocks. If each reader handles whole blocks at 68 MB/s, how many parallel readers are needed to finish within one hour, and how many blocks does each read?

Input
4 TB · 128 MB blocks · 68 MB/s · 1 hour
Expected output
32,768 blocks, 18 readers, about 1821 blocks each

Approach

  1. Blocks = size ÷ 128 MB.
  2. One reader reads 68 × 3600 MB per hour.
  3. Readers = ceil(size ÷ per-reader capacity); blocks each = blocks ÷ readers.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

9 Quiz and revision

Check yourself

Quick revision

Lesson 6.5 in one screen

  • Configuration → FileSystem.get(conf) → DistributedFileSystem for hdfs://.
  • Write: create(path, overwrite) → write → close (or hflush to make data visible).
  • Read: open(path) → seekable stream; reads from DataNodes.
  • Metadata: getFileStatus, listStatus, listFiles(dir, true), getFileBlockLocations.
  • rename and delete return booleans; delete skips the trash.
  • Lab: 300,000 orders in 1.3 s; 403 MB in 5.9 s (≈68 MB/s) through one client.
  • WebHDFS: REST on port 9870, OPEN redirects (307) to a DataNode on 9864.
  • Run with javac -cp $(hadoop classpath) and hadoop jar app.jar Class.

Interview and viva questions

How do you read a file from HDFS in Java?

Create a Configuration, get a FileSystem with FileSystem.get(conf), call fs.open(new Path("/path")) to get an FSDataInputStream, wrap it in a BufferedReader (or copy it with IOUtils.copyBytes), and close the stream. The client gets block locations from the NameNode and reads the bytes directly from DataNodes.

How do you write a file to HDFS in Java?

Call fs.create(path, overwrite) to get an FSDataOutputStream, write bytes, and close it. Closing completes the file on the NameNode. Use hflush() to make written data visible to readers before closing, and hsync() to also force it to disk on the DataNodes.

What is the difference between FileSystem.get() and FileSystem.newInstance()?

get() returns a cached instance shared across the JVM for the same scheme, authority and user; closing it affects every user of that instance. newInstance() always returns a new instance that you own and may close.

What is WebHDFS?

A REST API for HDFS served by the NameNode (port 9870 in Hadoop 3) and DataNodes (port 9864). Metadata operations are answered by the NameNode; for reading and writing data it redirects the client to a DataNode. It lets any language use HDFS over HTTP.

How does Spark know where HDFS blocks are?

Through the same FileSystem API: getFileBlockLocations (or listFiles with locations) returns the hosts of each block, and the scheduler prefers to run a task on one of those hosts.

Can the same Java code access S3?

Yes, if the S3A connector is on the classpath and configured: FileSystem.get(URI.create("s3a://bucket/"), conf) returns an S3AFileSystem with the same methods. Some semantics differ (for example rename is a copy on object stores, not an atomic metadata change).

Practice problems

API exercises using the real outputs from the lab programs.

P1Easy

Bytes in the demo file

HdfsBasics wrote the header and two order lines (with newline characters). Confirm the 49 bytes it reported.

Input
"order_id,item,amount\n3001,samosa,18\n3002,chai,15\n"
Expected output
49 bytes
Show solution and explanation
data = "order_id,item,amount\n" + "3001,samosa,18\n3002,chai,15\n"
print(len(data.encode("utf-8")), "bytes")
49 bytes

How it works: 49 bytes: 21 + 15 + 13, exactly the length getFileStatus reported.

P2Easy

Check the total

Add the six ItemCount results for canteen_orders.csv.

Input
50,170 · 49,948 · 49,984 · 49,891 · 50,021 · 49,986
Expected output
300000
Show solution and explanation
print(sum([50170, 49948, 49984, 49891, 50021, 49986]))
300000

How it works: 300,000 orders: every data line counted once, the header skipped.

P3Easy

Block lengths

orders_x30.csv is 140,760,270 bytes. Compute its block lengths with 128 MB blocks.

Input
140,760,270 bytes
Expected output
[134217728, 6542542]
Show solution and explanation
size, block = 140_760_270, 128 * 1024 * 1024
print([min(block, size - off) for off in range(0, size, block)])
[134217728, 6542542]

How it works: [134217728, 6542542]: the two BlockLocations printed by the real program.

P4Medium

Ratio for x90

orders_x90.csv is the 300,000-order file 90 times. Check that each item's count is exactly 90× and explain “item 89”.

Input
x1 and x90 counts
Expected output
True header lines inside: 89
Show solution and explanation
x1 = {"chai": 50170, "dosa": 49948, "idli": 49984, "poha": 49891, "samosa": 50021, "vada pav": 49986}
x90 = {"chai": 4515300, "dosa": 4495320, "idli": 4498560, "poha": 4490190, "samosa": 4501890, "vada pav": 4498740}
print(all(x90[k] == 90 * x1[k] for k in x1), "header lines inside:", 90 - 1)
True header lines inside: 89

How it works: True: every count is exactly 90×. The first header was skipped, the other 89 copies were counted as an item called “item”.

P5Medium

Parse WebHDFS JSON

From the real GETFILESTATUS reply, print length, replication, permission and block size in MB.

Input
{"FileStatus":{…}}
Expected output
47 3 640 128 MB
Show solution and explanation
import json
reply = '{"FileStatus":{"accessTime":1792579946689,"blockSize":134217728,"childrenNum":0,"fileId":16425,"group":"supergroup","length":47,"modificationTime":1792575484677,"owner":"hadoop","pathSuffix":"","permission":"640","replication":3,"storagePolicy":0,"type":"FILE"}}'
st = json.loads(reply)["FileStatus"]
print(st["length"], st["replication"], st["permission"], st["blockSize"] // 2**20, "MB")
47 3 640 128 MB

How it works: 47 bytes, replication 3, permission 640 (set with chmod in Lesson 6.3), 128 MB blocks.

P6Medium

Where does the redirect go?

Extract host, port, offset and NameNode address from the real 307 Location header.

Input
Location: http://46201bb62e68:9864/…
Expected output
46201bb62e68 9864 0 namenode:8020
Show solution and explanation
from urllib.parse import urlparse, parse_qs
loc = "http://46201bb62e68:9864/webhdfs/v1/user/hadoop/canteen/raw/day1.csv?op=OPEN&user.name=hadoop&namenoderpcaddress=namenode:8020&offset=0"
u = urlparse(loc); q = parse_qs(u.query)
print(u.hostname, u.port, q["offset"][0], q["namenoderpcaddress"][0])
46201bb62e68 9864 0 namenode:8020

How it works: DataNode 46201bb62e68 (datanode-2) on port 9864, offset 0, and the NameNode RPC address the DataNode should use.

P7Business problem

Service vs shell for an app

An app team pays Rs 12 per vCPU-hour. Their shell-based uploader uses 1 vCPU fully for 60 minutes a day (mostly JVM start-ups); an API-based version needs 2 minutes. What is the yearly saving, and what else improves?

Input
60 vs 2 min/day · Rs 12/vCPU-hour
Expected output
Rs 4,234 per year
Show solution and explanation
rate = 12
saved_hours = (60 - 2) / 60 * 365
print(f"Rs {saved_hours * rate:,.0f} per year")
Rs 4,234 per year

How it works: About Rs 4,234 a year in compute (illustrative), but the bigger wins are proper exceptions instead of parsing text, and uploads that finish in milliseconds.

P8Advanced

Locality-aware task plan

Given block locations (like getFileBlockLocations returns) and 3 worker nodes, assign each block to a worker that holds a replica, keeping the load as even as possible. Print the plan and how many blocks are local.

Input
6 blocks · hosts per block
Expected output
{'dn-1': [(0, 'local'), (4, 'local')], 'dn-2': [(1, 'local'), (3, 'local')], 'dn-3': [(2, 'local'), (5, 'local')]}
local: 6 of 6
Show solution and explanation
locs = {0: ["dn-1", "dn-2", "dn-3"], 1: ["dn-1", "dn-2", "dn-3"], 2: ["dn-1", "dn-3", "dn-4"],
        3: ["dn-2", "dn-4", "dn-5"], 4: ["dn-1", "dn-5", "dn-2"], 5: ["dn-4", "dn-5", "dn-3"]}
workers = {"dn-1": [], "dn-2": [], "dn-3": []}
for b, hosts in locs.items():
    local = [w for w in workers if w in hosts]
    pick = min(local or workers, key=lambda w: len(workers[w]))
    workers[pick].append((b, "local" if pick in hosts else "remote"))
print(workers)
print("local:", sum(t == "local" for v in workers.values() for _, t in v), "of", len(locs))
{'dn-1': [(0, 'local'), (4, 'local')], 'dn-2': [(1, 'local'), (3, 'local')], 'dn-3': [(2, 'local'), (5, 'local')]} local: 6 of 6

How it works: Every block finds a worker with a local replica here, two blocks each. This is the core idea of MapReduce scheduling (Module 8): move the computation to the data.

Real-world scenario questions (asked in company interviews)

API questions check whether you can go beyond the shell and understand what client code really does.

HDFS · JavaFresher

“Write code to copy a local file to HDFS”

A whiteboard coding question.

The interviewer is checking: Configuration, FileSystem, Path, closing resources.

Show a strong answer
  • Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(conf);
  • fs.copyFromLocalFile(new Path("orders.csv"), new Path("/user/hadoop/raw/"));, or fs.create() plus IOUtils.copyBytes() from a local input stream.
  • Close streams with try-with-resources; mention that the bytes go through the DataNode write pipeline.
Production · bugExperienced

A web service fails with “java.io.IOException: Filesystem closed”

It happens randomly, after some requests finish.

The interviewer is checking: the FileSystem cache.

Show a strong answer
  • Some code calls fs.close() on an instance from FileSystem.get(), which is cached and shared by all threads.
  • Fix: never close the shared instance (close only streams), or use FileSystem.newInstance() where code must own and close its instance.
DesignExperienced

A Python dashboard must read results from HDFS

No Hadoop installation on the dashboard servers, and a firewall in between.

The interviewer is checking: WebHDFS vs HttpFS.

Show a strong answer
  • WebHDFS works over HTTP, but OPEN redirects to DataNodes on port 9864, so every DataNode must be reachable.
  • Through a firewall, use HttpFS: a gateway that streams the data itself, so only one host and port are opened.
  • Keep results small (aggregated by Hive or Spark) so the dashboard never pulls raw data.
PerformanceFresher

“My Java program takes 17 hours to scan 4 TB. Why?”

A single program reads a big file from start to end.

The interviewer is checking: single client vs parallel processing.

Show a strong answer
  • One client reads one stream: in our lab about 68 MB/s, so 4 TB ≈ 17 hours.
  • HDFS can serve many readers at once. Split the work by blocks (getFileBlockLocations) and run readers on the nodes that hold them: that is MapReduce or Spark.