Build a QR Code Attendance System

Generate QR codes for event attendees and build a check-in system in Java.

Introduction

30 Second Summary

Running a workshop with 50+ attendees means someone is always stuck at the door with a clipboard, squinting at names. QR codes turn each attendee into a scannable, unique image that confirms their identity in seconds.

In this project, you will build a Java application that generates unique QR code images for event attendees and checks them in by reading those codes. The app tracks who has arrived, prevents duplicate check-ins, and exports a final attendance report.

What You'll Build

A console-based Java attendance system that reads attendee data from a CSV file, generates personalized QR code images, validates check-ins, and exports attendance reports.

By the end of this project, you'll have:

  • A Maven Java project with the ZXing QR code library configured as a dependency.
  • A QR code generator that creates unique PNG images for each attendee from a CSV file.
  • A check-in system that reads QR code images, validates attendees against your registration list, and prevents duplicate entries.
  • An attendance report that displays results in the console and exports them to a CSV file.
  • Secret Mission: Add a late arrival threshold that flags attendees who check in after a configurable cutoff time.

Are there any prerequisites?

You should be familiar with basic Java syntax including variables, loops, and if/else statements. No prior experience with Maven or external libraries is required.

Set Up Your Java Development Environment

Before you can build the QR code attendance system, you need a working Java development environment. This means having a code editor that understands Java, a compiler that turns your code into something runnable, and a build tool that manages external libraries.

In this step, you will install the Java extension pack in VS Code, set up JDK 21, and create a Maven project with the ZXing QR code library ready to use.

In this step, get ready to:
  • Install the Extension Pack for Java and JDK 21.
  • Create a Maven project for QR attendance.
  • Add the ZXing QR code library as a dependency.
Install the Extension Pack for Java in VS Code

The Extension Pack for Java by Microsoft bundles everything VS Code needs to work as a full Java IDE. It includes Language Support for Java by Red Hat, Debugger for Java, and Maven for Java.

  • Open the Extensions sidebar by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
  • Type Extension Pack for Java in the search bar.
  • Find the result published by Microsoft and click Install.

What does this extension pack include?

It bundles six extensions into one install: Language Support for Java (code completion, errors, refactoring), Debugger for Java, Test Runner, Maven for Java, Gradle for Java, and Project Manager for Java.

You get full IDE capabilities without leaving VS Code.

Now you need JDK 21 so the Java language server can run. The extension includes a built-in JDK installer that handles the download for you.

  • Open the Command Palette by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows).
  • Type Java: Install New JDK and select it.
  • Select JDK 21 from the version list.
  • Follow the prompts to complete the download and installation.

Why JDK 21?

The Java language server in VS Code requires JDK 21 as its minimum runtime version. Your project code itself targets Java 11 for broad compatibility, but the editor tooling needs 21 to function.

Verify that the JDK installed correctly.

  • Open the integrated terminal by pressing Ctrl+` (backtick key, both macOS and Windows).
  • Check your Java version by running this command:
java -version

✔️ I see version 21 or higher

Your JDK is ready to go. You should see output containing openjdk version "21 or a higher number. Move on to the next substep.

ⓧ I see an older version

Your system has an older JDK taking priority in the PATH. The Extension Pack for Java installer saves the new JDK path in VS Code settings, but your terminal may still point to the old one.

  • Close and reopen the VS Code terminal (Ctrl+` twice).
  • Run java -version again.
  • If it still shows an older version, open the Command Palette and run Java: Install New JDK again, selecting version 21.

ⓧ Command not found

The JDK installation did not complete, or the terminal cannot find the java binary.

  • Close and reopen VS Code entirely.
  • Open the Command Palette and run Java: Install New JDK to try the installation again.
  • If the installer does not appear, download JDK 21 manually from adoptium.net and run the installer for your operating system.
  • After installing, reopen the VS Code terminal and run java -version again.
Create a new Maven project

Maven is a build tool that manages your project structure and downloads external libraries automatically. You will use the VS Code Command Palette to scaffold a new Maven project with a standard folder layout.

What prompts will you see?

The project creation wizard asks several questions in your terminal. Each prompt is covered in a separate bullet below so you know exactly what to enter.

  • Open the Command Palette by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows).
  • Type Java: Create Java Project and select it.
  • Select Maven as the build tool.
  • Select maven-archetype-quickstart as the archetype.
  • When prompted to select a version for the archetype, choose the latest available (the top of the list).
  • Choose your Desktop as the destination folder.

The wizard now asks for project details in the terminal.

  • Enter com.attendance for the groupId prompt.
  • Enter qr-attendance for the artifactId prompt.
  • Press Enter to accept the default version (1.0-SNAPSHOT).
  • Press Enter to accept the default package (com.attendance).
  • Type Y and press Enter to confirm the configuration.

VS Code generates the project and may ask if you want to open it. Click Open to open the qr-attendance folder in VS Code.

What did Maven create?

Maven generated a standard folder structure: src/main/java/com/attendance/ for your application code and src/test/java/com/attendance/ for tests.

The pom.xml file at the project root is the configuration file where you declare dependencies and build settings.

  • Confirm the project structure exists by checking the Explorer sidebar on the left. You should see src, pom.xml, and other generated files.

Don't see the project structure?

If VS Code did not open the new project automatically, open it manually. Click File in the menu bar, then Open Folder, and select the qr-attendance folder on your Desktop.

Help me troubleshoot opening my Maven project in VS Code.

Add ZXing dependencies to pom.xml

ZXing ("Zebra Crossing") is an open-source library for reading and generating barcodes, including QR codes. You need to declare it as a dependency in your pom.xml so Maven downloads it automatically.

  • Open pom.xml from the Explorer sidebar (it is in the project root).
  • Select all the content in the file (Cmd+A on macOS or Ctrl+A on Windows) and replace it with this code:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.attendance</groupId>
    <artifactId>qr-attendance</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.5.4</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.5.4</version>
        </dependency>
    </dependencies>
</project>

What does this pom.xml do?

  • The <properties> block sets the Java compiler to target version 11, which gives you modern Java features while staying broadly compatible.
  • The <dependencies> block declares two ZXing modules: core (the barcode encoding/decoding engine) and javase (helpers for reading and writing image files on desktop Java).
  • Version 3.5.4 is the latest stable release of ZXing.
  • Save pom.xml by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • VS Code automatically downloads the ZXing dependencies in the background. Check the Java Projects section in the Explorer sidebar. Expand Project and External Dependencies to confirm you see zxing entries listed.

Dependencies not downloading?

If VS Code does not download dependencies automatically, open the Command Palette and run Java: Clean Java Language Server Workspace. Then reload the window when prompted. Maven will re-read pom.xml and download the missing libraries.

Help me fix Maven dependency download issues in VS Code.

✔️ Awesome, I've got everything!

Great. Double check you have saved pom.xml and that the ZXing dependencies appear under Project and External Dependencies in the sidebar.

ⓧ I'd like to double check the full code

Here is the complete pom.xml for comparison:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.attendance</groupId>
    <artifactId>qr-attendance</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.5.4</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.5.4</version>
        </dependency>
    </dependencies>
</project>

Your Java environment is ready and your project has the QR code library loaded. Next up, you will build the QR code generator that creates a unique PNG image for each attendee in your CSV file.

Build the QR Code Generator

Your Maven project is set up with ZXing ready to go. Now it's time to build the part of your app that actually generates QR codes for your attendees.

In this step, you'll create a CSV file with attendee data, then write a Java class that reads that file and generates a unique QR code image for each person.

In this step, get ready to:
  • Create the attendee CSV data file.
  • Write the QRCodeGenerator class.
  • Run the generator and verify QR code output.
Create the attendee CSV file

Your QR code generator needs a source of attendee data. A CSV file is a simple, human-readable format that you can edit in any spreadsheet app or text editor.

  • In the VS Code Explorer sidebar, right-click on the qr-attendance project root folder (the same level as pom.xml).
  • Select New File.
  • Name the file attendees.csv and press Enter.
  • Add the following content to the file:
id,name,email
ATT001,Alice Johnson,alice@example.com
ATT002,Bob Smith,bob@example.com
ATT003,Carol Davis,carol@example.com
ATT004,David Lee,david@example.com
ATT005,Eva Martinez,eva@example.com

What does this data represent?

Each row represents one registered attendee for your workshop. The id column is the unique identifier that gets encoded into each QR code.

The name and email columns provide human-readable info for your attendance reports later.

  • Save attendees.csv.

✔️ Awesome, I've got everything!

Great. Make sure you have exactly 5 attendees listed (ATT001 through ATT005) with the header row on the first line.

ⓧ I'd like to double check the full code

id,name,email
ATT001,Alice Johnson,alice@example.com
ATT002,Bob Smith,bob@example.com
ATT003,Carol Davis,carol@example.com
ATT004,David Lee,david@example.com
ATT005,Eva Martinez,eva@example.com
Write the QRCodeGenerator class

This class is the core engine of your app. It reads the CSV file line by line, and for each attendee, encodes their ID into a 300x300 pixel QR code PNG image.

  • In the Explorer sidebar, navigate to src/main/java/com/attendance/.
  • Right-click the attendance folder and select New File.
  • Name the file QRCodeGenerator.java and press Enter.
  • Add the following code to the file:
package com.attendance;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class QRCodeGenerator {

    private static final int QR_WIDTH = 300;
    private static final int QR_HEIGHT = 300;
    private static final String OUTPUT_DIR = "qrcodes";

    public static void generateQRCodes(String csvFilePath) {
        Path outputPath = Paths.get(OUTPUT_DIR);
        try {
            Files.createDirectories(outputPath);
        } catch (IOException e) {
            System.out.println("Error creating output directory: " + e.getMessage());
            return;
        }

        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        int count = 0;

        try (BufferedReader reader = new BufferedReader(new FileReader(csvFilePath))) {
            String line = reader.readLine(); // skip header
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(",");
                if (parts.length >= 3) {
                    String id = parts[0].trim();
                    String name = parts[1].trim();

                    BitMatrix bitMatrix = qrCodeWriter.encode(id, BarcodeFormat.QR_CODE, QR_WIDTH, QR_HEIGHT);
                    Path filePath = outputPath.resolve(id + ".png");
                    MatrixToImageWriter.writeToPath(bitMatrix, "PNG", filePath);

                    System.out.println("Generated QR code for: " + name + " (" + id + ")");
                    count++;
                }
            }
        } catch (IOException e) {
            System.out.println("Error reading CSV file: " + e.getMessage());
        } catch (WriterException e) {
            System.out.println("Error generating QR code: " + e.getMessage());
        }

        System.out.println("\nTotal QR codes generated: " + count);
        System.out.println("Saved to: " + outputPath.toAbsolutePath());
    }
}

What does this code do?

The class defines three constants: QR_WIDTH and QR_HEIGHT set each QR image to 300x300 pixels, and OUTPUT_DIR specifies the folder where images are saved.

The generateQRCodes method first creates the output directory, then opens the CSV with a BufferedReader. It skips the header row and loops through each attendee line.

For each attendee, ZXing's QRCodeWriter encodes the attendee ID into a BitMatrix (a grid of black and white pixels). Then MatrixToImageWriter writes that matrix to a PNG file named after the attendee ID.

  • Save QRCodeGenerator.java.
  • Watch the bottom-left status bar in VS Code. After a few seconds, the Java Language Server should finish compiling with no red error indicators in the file.

Seeing red underlines in the code?

If you see errors under the ZXing imports, the dependencies may not have downloaded yet. Click the Maven icon in the left sidebar, expand your project, and click the refresh icon to reload dependencies.

If the error says the package does not exist, check that your pom.xml has both the core and javase ZXing dependencies with version 3.5.4.

help me fix ZXing import errors in my QRCodeGenerator class

✔️ Awesome, I've got everything!

Great. Double check you have saved your file and there are no red underlines in the editor.

ⓧ I'd like to double check the full code

package com.attendance;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class QRCodeGenerator {

    private static final int QR_WIDTH = 300;
    private static final int QR_HEIGHT = 300;
    private static final String OUTPUT_DIR = "qrcodes";

    public static void generateQRCodes(String csvFilePath) {
        Path outputPath = Paths.get(OUTPUT_DIR);
        try {
            Files.createDirectories(outputPath);
        } catch (IOException e) {
            System.out.println("Error creating output directory: " + e.getMessage());
            return;
        }

        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        int count = 0;

        try (BufferedReader reader = new BufferedReader(new FileReader(csvFilePath))) {
            String line = reader.readLine(); // skip header
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(",");
                if (parts.length >= 3) {
                    String id = parts[0].trim();
                    String name = parts[1].trim();

                    BitMatrix bitMatrix = qrCodeWriter.encode(id, BarcodeFormat.QR_CODE, QR_WIDTH, QR_HEIGHT);
                    Path filePath = outputPath.resolve(id + ".png");
                    MatrixToImageWriter.writeToPath(bitMatrix, "PNG", filePath);

                    System.out.println("Generated QR code for: " + name + " (" + id + ")");
                    count++;
                }
            }
        } catch (IOException e) {
            System.out.println("Error reading CSV file: " + e.getMessage());
        } catch (WriterException e) {
            System.out.println("Error generating QR code: " + e.getMessage());
        }

        System.out.println("\nTotal QR codes generated: " + count);
        System.out.println("Saved to: " + outputPath.toAbsolutePath());
    }
}
Run the generator and verify output

Your class is written, but you need to run it to actually produce the QR code images. You'll add a temporary main method for testing purposes.

  • In QRCodeGenerator.java, scroll to the bottom of the class.
  • Add this temporary main method just before the final closing brace }:
    // Temporary main method for testing
    public static void main(String[] args) {
        generateQRCodes("attendees.csv");
    }

Why a temporary main method?

Java needs a main method as an entry point to run a class. You are adding one temporarily to test the generator. In the next step, you'll build the full App.java class that calls generateQRCodes from a menu, so you'll remove this test method afterwards.

  • Save the file.
  • Look for the Run text that appears above the main method in the editor. Click Run to execute the class.

What should I see?

The integrated terminal at the bottom of VS Code should display output like this:

Generated QR code for: Alice Johnson (ATT001)
Generated QR code for: Bob Smith (ATT002)
Generated QR code for: Carol Davis (ATT003)
Generated QR code for: David Lee (ATT004)
Generated QR code for: Eva Martinez (ATT005)

Total QR codes generated: 5
Saved to: /your/path/qr-attendance/qrcodes

The exact path at the end will match your project location on your machine.

  • In the Explorer sidebar, expand the new qrcodes folder. You should see 5 PNG files: ATT001.png through ATT005.png.
  • Click on any of the PNG files to open it in VS Code. You should see a black-and-white QR code image.

Don't see any QR code files?

If the terminal shows an error about the CSV file not being found, make sure attendees.csv is in the project root folder (the same level as pom.xml), not inside src/.

If you see a class not found error, make sure you saved the file and that the Run link appeared above the main method before clicking it.

help me fix my QRCodeGenerator so it produces the QR code PNG files

  • Now that you have confirmed the generator works, remove the temporary main method and its comment from QRCodeGenerator.java.
  • Save the file.

Your QR codes are generated and ready to go. Next up, you'll build the check-in system that reads those QR code images and tracks who has arrived at your event.

Build the Check-In System

You now have a folder full of QR code images, one for each attendee in your workshop. The next challenge is reading those QR codes back and matching them to real people.

In this step, you will build the check-in system that decodes QR images, validates attendees against your registration list, and prevents duplicate check-ins. You will also create the main application menu that ties all the pieces together.

In this step, get ready to:
  • Track attendee check-ins with timestamps and duplicate prevention.
  • Read QR code images to extract attendee IDs using ZXing.
  • Wire up a console menu that connects generation, check-in, and reporting.
Write the AttendanceTracker class

The attendance tracker is the brain of your system. It loads attendee data into memory, records who has checked in (and when), blocks duplicate check-ins, and produces reports.

  • In VS Code, right-click the src/main/java/com/attendance/ folder in the Explorer sidebar.
  • Select New File and name it AttendanceTracker.java.
  • Paste the following code into the new file:
package com.attendance;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AttendanceTracker {

    private Map<String, String[]> attendees; // id -> [name, email]
    private Map<String, LocalDateTime> checkIns; // id -> check-in time
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public AttendanceTracker() {
        this.attendees = new HashMap<>();
        this.checkIns = new HashMap<>();
    }

    public void loadAttendees(String csvFilePath) {
        try (BufferedReader reader = new BufferedReader(new FileReader(csvFilePath))) {
            String line = reader.readLine(); // skip header
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(",");
                if (parts.length >= 3) {
                    String id = parts[0].trim();
                    String name = parts[1].trim();
                    String email = parts[2].trim();
                    attendees.put(id, new String[]{name, email});
                }
            }
            System.out.println("Loaded " + attendees.size() + " attendees.");
        } catch (IOException e) {
            System.out.println("Error loading attendees: " + e.getMessage());
        }
    }

    public String checkIn(String attendeeId) {
        if (!attendees.containsKey(attendeeId)) {
            return "INVALID: Attendee ID '" + attendeeId + "' not found in registration list.";
        }

        if (checkIns.containsKey(attendeeId)) {
            String name = attendees.get(attendeeId)[0];
            String time = checkIns.get(attendeeId).format(FORMATTER);
            return "DUPLICATE: " + name + " already checked in at " + time + ".";
        }

        LocalDateTime now = LocalDateTime.now();
        checkIns.put(attendeeId, now);
        String name = attendees.get(attendeeId)[0];
        return "SUCCESS: " + name + " checked in at " + now.format(FORMATTER) + ".";
    }

    public void printReport() {
        int total = attendees.size();
        int checkedIn = checkIns.size();
        int absent = total - checkedIn;

        System.out.println("\n===== ATTENDANCE REPORT =====");
        System.out.println("Total Registered: " + total);
        System.out.println("Checked In:       " + checkedIn);
        System.out.println("Absent:           " + absent);
        System.out.println("=============================\n");

        System.out.printf("%-8s %-20s %-10s %-20s%n", "ID", "Name", "Status", "Check-in Time");
        System.out.println("-".repeat(60));

        for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
            String id = entry.getKey();
            String name = entry.getValue()[0];
            String status;
            String time;

            if (checkIns.containsKey(id)) {
                status = "PRESENT";
                time = checkIns.get(id).format(FORMATTER);
            } else {
                status = "ABSENT";
                time = "-";
            }

            System.out.printf("%-8s %-20s %-10s %-20s%n", id, name, status, time);
        }
    }

    public void exportReport(String outputFilePath) {
        try (PrintWriter writer = new PrintWriter(new FileWriter(outputFilePath))) {
            writer.println("id,name,email,status,checkin_time");

            for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
                String id = entry.getKey();
                String name = entry.getValue()[0];
                String email = entry.getValue()[1];
                String status;
                String time;

                if (checkIns.containsKey(id)) {
                    status = "PRESENT";
                    time = checkIns.get(id).format(FORMATTER);
                } else {
                    status = "ABSENT";
                    time = "";
                }

                writer.println(id + "," + name + "," + email + "," + status + "," + time);
            }

            System.out.println("Report exported to: " + outputFilePath);
        } catch (IOException e) {
            System.out.println("Error exporting report: " + e.getMessage());
        }
    }

    public int getTotalAttendees() {
        return attendees.size();
    }

    public int getCheckedInCount() {
        return checkIns.size();
    }
}

What does this class do?

The attendees HashMap stores each person's ID mapped to their name and email. The checkIns HashMap records which IDs have checked in and at what time.

The checkIn() method handles three scenarios: an unknown ID returns INVALID, a repeated ID returns DUPLICATE, and a valid first-time ID returns SUCCESS with a timestamp.

The printReport() method outputs a formatted table to the console. The exportReport() method writes the same data to a CSV file for permanent records.

  • Save AttendanceTracker.java.
  • You won't see a change yet. The next class (QRCodeReader) and the main app will use this tracker.

Seeing red underlines in VS Code?

If you see errors on the import lines, VS Code's Java Language Server may still be loading. Wait 10-20 seconds for the imports to resolve.

If errors persist, check that your pom.xml still has both ZXing dependencies. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P) and run Java: Clean Java Language Server Workspace.

Why is my AttendanceTracker showing compile errors?

✔️ Awesome, I've got everything!

Great. Make sure you have saved the file before moving on.

ⓧ I'd like to double check the full code

package com.attendance;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AttendanceTracker {

    private Map<String, String[]> attendees; // id -> [name, email]
    private Map<String, LocalDateTime> checkIns; // id -> check-in time
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public AttendanceTracker() {
        this.attendees = new HashMap<>();
        this.checkIns = new HashMap<>();
    }

    public void loadAttendees(String csvFilePath) {
        try (BufferedReader reader = new BufferedReader(new FileReader(csvFilePath))) {
            String line = reader.readLine(); // skip header
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(",");
                if (parts.length >= 3) {
                    String id = parts[0].trim();
                    String name = parts[1].trim();
                    String email = parts[2].trim();
                    attendees.put(id, new String[]{name, email});
                }
            }
            System.out.println("Loaded " + attendees.size() + " attendees.");
        } catch (IOException e) {
            System.out.println("Error loading attendees: " + e.getMessage());
        }
    }

    public String checkIn(String attendeeId) {
        if (!attendees.containsKey(attendeeId)) {
            return "INVALID: Attendee ID '" + attendeeId + "' not found in registration list.";
        }

        if (checkIns.containsKey(attendeeId)) {
            String name = attendees.get(attendeeId)[0];
            String time = checkIns.get(attendeeId).format(FORMATTER);
            return "DUPLICATE: " + name + " already checked in at " + time + ".";
        }

        LocalDateTime now = LocalDateTime.now();
        checkIns.put(attendeeId, now);
        String name = attendees.get(attendeeId)[0];
        return "SUCCESS: " + name + " checked in at " + now.format(FORMATTER) + ".";
    }

    public void printReport() {
        int total = attendees.size();
        int checkedIn = checkIns.size();
        int absent = total - checkedIn;

        System.out.println("\n===== ATTENDANCE REPORT =====");
        System.out.println("Total Registered: " + total);
        System.out.println("Checked In:       " + checkedIn);
        System.out.println("Absent:           " + absent);
        System.out.println("=============================\n");

        System.out.printf("%-8s %-20s %-10s %-20s%n", "ID", "Name", "Status", "Check-in Time");
        System.out.println("-".repeat(60));

        for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
            String id = entry.getKey();
            String name = entry.getValue()[0];
            String status;
            String time;

            if (checkIns.containsKey(id)) {
                status = "PRESENT";
                time = checkIns.get(id).format(FORMATTER);
            } else {
                status = "ABSENT";
                time = "-";
            }

            System.out.printf("%-8s %-20s %-10s %-20s%n", id, name, status, time);
        }
    }

    public void exportReport(String outputFilePath) {
        try (PrintWriter writer = new PrintWriter(new FileWriter(outputFilePath))) {
            writer.println("id,name,email,status,checkin_time");

            for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
                String id = entry.getKey();
                String name = entry.getValue()[0];
                String email = entry.getValue()[1];
                String status;
                String time;

                if (checkIns.containsKey(id)) {
                    status = "PRESENT";
                    time = checkIns.get(id).format(FORMATTER);
                } else {
                    status = "ABSENT";
                    time = "";
                }

                writer.println(id + "," + name + "," + email + "," + status + "," + time);
            }

            System.out.println("Report exported to: " + outputFilePath);
        } catch (IOException e) {
            System.out.println("Error exporting report: " + e.getMessage());
        }
    }

    public int getTotalAttendees() {
        return attendees.size();
    }

    public int getCheckedInCount() {
        return checkIns.size();
    }
}
Write the QRCodeReader class

The generator encoded attendee IDs into QR images. Now you need the reverse: a reader that decodes a QR code image back into the attendee ID string.

  • Right-click the src/main/java/com/attendance/ folder again.
  • Select New File and name it QRCodeReader.java.
  • Paste the following code into the new file:
package com.attendance;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class QRCodeReader {

    public static String readQRCode(String filePath) {
        try {
            BufferedImage image = ImageIO.read(new File(filePath));
            if (image == null) {
                System.out.println("Error: Could not read image file.");
                return null;
            }

            BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

            Result result = new MultiFormatReader().decode(bitmap);
            return result.getText();

        } catch (NotFoundException e) {
            System.out.println("Error: No QR code found in the image.");
            return null;
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
            return null;
        }
    }
}

How does QR decoding work?

ZXing's decode pipeline has three stages. First, BufferedImageLuminanceSource converts the PNG into a brightness map (luminance values).

Next, HybridBinarizer turns those brightness values into pure black-and-white pixels that represent the QR pattern. Finally, MultiFormatReader interprets the pattern and returns the encoded text.

  • Save QRCodeReader.java.
  • You won't see a change yet. The next file (App.java) ties the reader and tracker together so you can test everything.

Import errors on ZXing classes?

Make sure your pom.xml includes both the core and javase ZXing dependencies. The BufferedImageLuminanceSource class lives in the javase module, not core.

Why can't VS Code find the ZXing imports in my QRCodeReader?

✔️ Awesome, I've got everything!

Great. Make sure you have saved the file before moving on.

ⓧ I'd like to double check the full code

package com.attendance;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class QRCodeReader {

    public static String readQRCode(String filePath) {
        try {
            BufferedImage image = ImageIO.read(new File(filePath));
            if (image == null) {
                System.out.println("Error: Could not read image file.");
                return null;
            }

            BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

            Result result = new MultiFormatReader().decode(bitmap);
            return result.getText();

        } catch (NotFoundException e) {
            System.out.println("Error: No QR code found in the image.");
            return null;
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
            return null;
        }
    }
}
Wire up the check-in flow

You have the tracker and the reader. Now you need a main application class that presents a menu and connects all the pieces into a usable workflow.

  • In the src/main/java/com/attendance/ folder, find the existing App.java file that Maven generated.
  • Replace its entire contents with the following code:
package com.attendance;

import java.util.Scanner;

public class App {

    private static final String CSV_FILE = "attendees.csv";
    private static final String REPORT_FILE = "report.csv";

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        AttendanceTracker tracker = new AttendanceTracker();
        tracker.loadAttendees(CSV_FILE);

        boolean running = true;

        while (running) {
            System.out.println("\n===== QR ATTENDANCE SYSTEM =====");
            System.out.println("1. Generate QR Codes");
            System.out.println("2. Check In Attendee");
            System.out.println("3. View Attendance Report");
            System.out.println("4. Export Report to CSV");
            System.out.println("5. Exit");
            System.out.print("Choose an option: ");

            String choice = scanner.nextLine().trim();

            switch (choice) {
                case "1":
                    QRCodeGenerator.generateQRCodes(CSV_FILE);
                    break;
                case "2":
                    System.out.print("Enter QR code image path (e.g., qrcodes/ATT001.png): ");
                    String path = scanner.nextLine().trim();
                    String attendeeId = QRCodeReader.readQRCode(path);
                    if (attendeeId != null) {
                        String result = tracker.checkIn(attendeeId);
                        System.out.println(result);
                    }
                    break;
                case "3":
                    tracker.printReport();
                    break;
                case "4":
                    tracker.exportReport(REPORT_FILE);
                    break;
                case "5":
                    running = false;
                    System.out.println("Goodbye!");
                    break;
                default:
                    System.out.println("Invalid option. Please try again.");
            }
        }

        scanner.close();
    }
}

What does the menu do?

The while (running) loop keeps the menu visible until the user picks Exit. Each option calls a method you already wrote in a previous class.

Option 2 is the check-in flow: it asks for a QR code file path, passes it to QRCodeReader.readQRCode() to decode the attendee ID, then hands that ID to tracker.checkIn() which validates and records the check-in.

  • Save App.java.
  • Click the Run button (the play icon) above the main method, or right-click the file and select Run Java.
  • You should see the menu appear in the terminal with the message Loaded 5 attendees. printed above it.
  • Type 2 and press Enter to select Check In Attendee.
  • When prompted for the QR code image path, type qrcodes/ATT001.png and press Enter.
  • You should see a SUCCESS message with Alice Johnson's name and a timestamp.
  • Try checking in the same attendee again (qrcodes/ATT001.png). You should see a DUPLICATE message this time.
  • Check in 2-3 more attendees (qrcodes/ATT002.png, qrcodes/ATT003.png) to confirm they each show SUCCESS.
  • Type 5 and press Enter to exit the app.

App won't run or shows an error?

If you see a "cannot find symbol" error mentioning AttendanceTracker or QRCodeReader, make sure all three .java files are saved and share the same package declaration: package com.attendance;.

If the app runs but check-in returns an error about reading the file, make sure you are running the app from the project root (where attendees.csv and the qrcodes/ folder live). VS Code's Run Java button handles this automatically.

My QR attendance app is throwing an error when I try to check in an attendee. Help me debug.

✔️ Awesome, I've got everything!

Great. Make sure you have saved the file before moving on.

ⓧ I'd like to double check the full code

package com.attendance;

import java.util.Scanner;

public class App {

    private static final String CSV_FILE = "attendees.csv";
    private static final String REPORT_FILE = "report.csv";

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        AttendanceTracker tracker = new AttendanceTracker();
        tracker.loadAttendees(CSV_FILE);

        boolean running = true;

        while (running) {
            System.out.println("\n===== QR ATTENDANCE SYSTEM =====");
            System.out.println("1. Generate QR Codes");
            System.out.println("2. Check In Attendee");
            System.out.println("3. View Attendance Report");
            System.out.println("4. Export Report to CSV");
            System.out.println("5. Exit");
            System.out.print("Choose an option: ");

            String choice = scanner.nextLine().trim();

            switch (choice) {
                case "1":
                    QRCodeGenerator.generateQRCodes(CSV_FILE);
                    break;
                case "2":
                    System.out.print("Enter QR code image path (e.g., qrcodes/ATT001.png): ");
                    String path = scanner.nextLine().trim();
                    String attendeeId = QRCodeReader.readQRCode(path);
                    if (attendeeId != null) {
                        String result = tracker.checkIn(attendeeId);
                        System.out.println(result);
                    }
                    break;
                case "3":
                    tracker.printReport();
                    break;
                case "4":
                    tracker.exportReport(REPORT_FILE);
                    break;
                case "5":
                    running = false;
                    System.out.println("Goodbye!");
                    break;
                default:
                    System.out.println("Invalid option. Please try again.");
            }
        }

        scanner.close();
    }
}

Your check-in system is fully functional. You can scan QR codes, validate attendees, and block duplicates. Next up, you will put the reporting features to the test and export a permanent attendance record.

Export the Attendance Report

Your check-in system captures who arrives, but after your workshop ends, you need a clear picture of attendance. A formatted report tells you at a glance how many people showed up versus how many registered.

In this step, you will walk through the report methods already in your app and put the entire system through a full end-to-end test. By the end, you will have a report.csv file proving the whole pipeline works.

In this step, get ready to:
  • Review the console attendance report method.
  • Review the CSV export method.
  • Test the complete workflow end-to-end.
Review the attendance report method

Your AttendanceTracker class already has a printReport() method that generates a formatted console report. Let's walk through how it works.

  • Switch back to AttendanceTracker.java in VS Code.
  • Scroll down to the printReport() method. Here is what it contains:
public void printReport() {
    int total = attendees.size();
    int checkedIn = checkIns.size();
    int absent = total - checkedIn;

    System.out.println("\n===== ATTENDANCE REPORT =====");
    System.out.println("Total Registered: " + total);
    System.out.println("Checked In:       " + checkedIn);
    System.out.println("Absent:           " + absent);
    System.out.println("=============================\n");

    System.out.printf("%-8s %-20s %-10s %-20s%n", "ID", "Name", "Status", "Check-in Time");
    System.out.println("-".repeat(60));

    for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
        String id = entry.getKey();
        String name = entry.getValue()[0];
        String status;
        String time;

        if (checkIns.containsKey(id)) {
            status = "PRESENT";
            time = checkIns.get(id).format(FORMATTER);
        } else {
            status = "ABSENT";
            time = "-";
        }

        System.out.printf("%-8s %-20s %-10s %-20s%n", id, name, status, time);
    }
}

What does this method do?

The method calculates three summary statistics: total registered (from the attendees map size), total checked in (from the checkIns map size), and total absent (the difference).

It then prints a formatted table using printf with fixed-width columns. Each attendee row shows their ID, name, status (PRESENT or ABSENT), and the timestamp of when they checked in.

Review the CSV export method

A console report is useful during the event, but you also need a permanent file you can share with organizers or import into a spreadsheet. The exportReport() method handles this.

  • In the same file, scroll down to the exportReport() method:
public void exportReport(String outputFilePath) {
    try (PrintWriter writer = new PrintWriter(new FileWriter(outputFilePath))) {
        // Write CSV header row
        writer.println("id,name,email,status,checkin_time");

        for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
            String id = entry.getKey();
            String name = entry.getValue()[0];
            String email = entry.getValue()[1];
            String status;
            String time;

            if (checkIns.containsKey(id)) {
                status = "PRESENT";
                time = checkIns.get(id).format(FORMATTER);
            } else {
                status = "ABSENT";
                time = "";
            }

            // Write one row per attendee
            writer.println(id + "," + name + "," + email + "," + status + "," + time);
        }

        System.out.println("Report exported to: " + outputFilePath);
    } catch (IOException e) {
        System.out.println("Error exporting report: " + e.getMessage());
    }
}

What does this method do?

This method creates a CSV file with five columns: id, name, email, status, and checkin_time.

It uses a PrintWriter wrapped in a try-with-resources block, which automatically closes the file when done. Each attendee gets one row with their check-in status and timestamp.

Why CSV instead of a database?

CSV files are human-readable, editable in any spreadsheet app, and require zero database setup. For tracking 50-100 attendees at a workshop, a CSV is perfectly adequate and easy to share with co-organizers.

Test the complete workflow end-to-end

Now it is time to run through the entire attendance system from start to finish. You will generate QR codes, check in several attendees, view the console report, and export the results to a CSV file.

  • Run App.java by clicking the Run button above the main method (or right-click the file and select Run Java).
  • Type 1 and press Enter to generate QR codes.
  • Type 2 and press Enter to check in an attendee.
  • When prompted for the QR code image path, enter qrcodes/ATT001.png and press Enter.

You should see a success message confirming Alice Johnson has been checked in.

  • Repeat option 2 two more times, entering qrcodes/ATT002.png and qrcodes/ATT003.png as the paths.
  • Try checking in qrcodes/ATT001.png a second time to confirm the duplicate prevention works.

You should see a DUPLICATE message with the original check-in timestamp.

  • Type 3 and press Enter to view the attendance report.

You should see a formatted table showing 5 total registered, 3 checked in, and 2 absent. Each row displays the attendee's ID, name, PRESENT/ABSENT status, and check-in time.

What should I see?

Your console output includes a summary header with totals and a per-attendee table. ATT001, ATT002, and ATT003 show PRESENT with timestamps. ATT004 and ATT005 show ABSENT with a dash for the time.

  • Type 4 and press Enter to export the report to CSV.

You should see the message Report exported to: report.csv in the console.

  • Type 5 and press Enter to exit the app.
  • Open report.csv in the VS Code Explorer sidebar to verify its contents.

The file should contain a header row followed by one row per attendee, with PRESENT or ABSENT status and timestamps for those who checked in.

Don't see report.csv?

Make sure you selected option 4 (Export Report to CSV) before exiting. The file is written to the project root, which is the same folder that contains pom.xml.

If the Explorer sidebar does not show the file, right-click the project folder and select Refresh.

help me find my report.csv file

✔️ Awesome, I've got everything!

Great. Double check you have saved all your files and that report.csv exists in your project root.

ⓧ I'd like to double check the full code

Here is the complete AttendanceTracker.java for reference:

package com.attendance;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AttendanceTracker {

    private Map<String, String[]> attendees; // id -> [name, email]
    private Map<String, LocalDateTime> checkIns; // id -> check-in time
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public AttendanceTracker() {
        this.attendees = new HashMap<>();
        this.checkIns = new HashMap<>();
    }

    public void loadAttendees(String csvFilePath) {
        try (BufferedReader reader = new BufferedReader(new FileReader(csvFilePath))) {
            String line = reader.readLine(); // skip header
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(",");
                if (parts.length >= 3) {
                    String id = parts[0].trim();
                    String name = parts[1].trim();
                    String email = parts[2].trim();
                    attendees.put(id, new String[]{name, email});
                }
            }
            System.out.println("Loaded " + attendees.size() + " attendees.");
        } catch (IOException e) {
            System.out.println("Error loading attendees: " + e.getMessage());
        }
    }

    public String checkIn(String attendeeId) {
        if (!attendees.containsKey(attendeeId)) {
            return "INVALID: Attendee ID '" + attendeeId + "' not found in registration list.";
        }

        if (checkIns.containsKey(attendeeId)) {
            String name = attendees.get(attendeeId)[0];
            String time = checkIns.get(attendeeId).format(FORMATTER);
            return "DUPLICATE: " + name + " already checked in at " + time + ".";
        }

        LocalDateTime now = LocalDateTime.now();
        checkIns.put(attendeeId, now);
        String name = attendees.get(attendeeId)[0];
        return "SUCCESS: " + name + " checked in at " + now.format(FORMATTER) + ".";
    }

    public void printReport() {
        int total = attendees.size();
        int checkedIn = checkIns.size();
        int absent = total - checkedIn;

        System.out.println("\n===== ATTENDANCE REPORT =====");
        System.out.println("Total Registered: " + total);
        System.out.println("Checked In:       " + checkedIn);
        System.out.println("Absent:           " + absent);
        System.out.println("=============================\n");

        System.out.printf("%-8s %-20s %-10s %-20s%n", "ID", "Name", "Status", "Check-in Time");
        System.out.println("-".repeat(60));

        for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
            String id = entry.getKey();
            String name = entry.getValue()[0];
            String status;
            String time;

            if (checkIns.containsKey(id)) {
                status = "PRESENT";
                time = checkIns.get(id).format(FORMATTER);
            } else {
                status = "ABSENT";
                time = "-";
            }

            System.out.printf("%-8s %-20s %-10s %-20s%n", id, name, status, time);
        }
    }

    public void exportReport(String outputFilePath) {
        try (PrintWriter writer = new PrintWriter(new FileWriter(outputFilePath))) {
            writer.println("id,name,email,status,checkin_time");

            for (Map.Entry<String, String[]> entry : attendees.entrySet()) {
                String id = entry.getKey();
                String name = entry.getValue()[0];
                String email = entry.getValue()[1];
                String status;
                String time;

                if (checkIns.containsKey(id)) {
                    status = "PRESENT";
                    time = checkIns.get(id).format(FORMATTER);
                } else {
                    status = "ABSENT";
                    time = "";
                }

                writer.println(id + "," + name + "," + email + "," + status + "," + time);
            }

            System.out.println("Report exported to: " + outputFilePath);
        } catch (IOException e) {
            System.out.println("Error exporting report: " + e.getMessage());
        }
    }

    public int getTotalAttendees() {
        return attendees.size();
    }

    public int getCheckedInCount() {
        return checkIns.size();
    }
}

And here is the complete App.java:

package com.attendance;

import java.util.Scanner;

public class App {

    private static final String CSV_FILE = "attendees.csv";
    private static final String REPORT_FILE = "report.csv";

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        AttendanceTracker tracker = new AttendanceTracker();
        tracker.loadAttendees(CSV_FILE);

        boolean running = true;

        while (running) {
            System.out.println("\n===== QR ATTENDANCE SYSTEM =====");
            System.out.println("1. Generate QR Codes");
            System.out.println("2. Check In Attendee");
            System.out.println("3. View Attendance Report");
            System.out.println("4. Export Report to CSV");
            System.out.println("5. Exit");
            System.out.print("Choose an option: ");

            String choice = scanner.nextLine().trim();

            switch (choice) {
                case "1":
                    QRCodeGenerator.generateQRCodes(CSV_FILE);
                    break;
                case "2":
                    System.out.print("Enter QR code image path (e.g., qrcodes/ATT001.png): ");
                    String path = scanner.nextLine().trim();
                    String attendeeId = QRCodeReader.readQRCode(path);
                    if (attendeeId != null) {
                        String result = tracker.checkIn(attendeeId);
                        System.out.println(result);
                    }
                    break;
                case "3":
                    tracker.printReport();
                    break;
                case "4":
                    tracker.exportReport(REPORT_FILE);
                    break;
                case "5":
                    running = false;
                    System.out.println("Goodbye!");
                    break;
                default:
                    System.out.println("Invalid option. Please try again.");
            }
        }

        scanner.close();
    }
}

Your attendance system is fully functional. You can now generate QR codes for any event, check in attendees by scanning their codes, and produce a permanent record of who showed up. Next up, there is a Secret Mission that adds late arrival detection to your system.

Secret mission

Add Late Arrival Detection

Your attendance system tracks who showed up, but what about the people who showed up late? Add a configurable time threshold that automatically flags attendees who check in after the cutoff. Your reports will show exactly who was on time and who kept everyone waiting.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The qr-attendance project folder on your Desktop (contains all source code, CSV data, and generated files).
  • Generated QR code PNG images in the qrcodes/ folder.
  • The exported report.csv file.
  • Maven dependency cache in ~/.m2/repository (shared across all Maven projects on your machine).

Keep everything running

No action needed. Choose this if you plan to use this tool at your next workshop or want to keep developing it further.

  • Your project stays on your machine ready to go. Just update attendees.csv with your real attendee list before your next event.
  • The Maven cache in ~/.m2/repository is shared across all your Java projects. Keeping it saves download time for future Maven builds.
  • Delete the qrcodes/ folder and report.csv between events to start fresh each time.

Pause - I'll come back to this later

Close VS Code to free up memory. Your project files stay on disk and are ready to reopen whenever you need them.

  • Close VS Code.
  • Optionally delete the generated output files (qrcodes/ folder and report.csv) to free up a small amount of disk space. The app regenerates them on demand.
  • When you return, reopen the qr-attendance folder in VS Code and pick up where you left off.

Delete - I don't want to use this again

Remove all project files from your machine. This is permanent.

  • Delete the entire project folder by running this command in your terminal:

macOS

rm -rf ~/Desktop/qr-attendance

Windows

rmdir /s /q %USERPROFILE%\Desktop\qr-attendance
  • Optionally clear the Maven dependency cache to reclaim disk space:

macOS

rm -rf ~/.m2/repository/com/google/zxing

Windows

rmdir /s /q %USERPROFILE%\.m2\repository\com\google\zxing

Why only the ZXing cache?

The command above removes only the ZXing libraries this project downloaded. Deleting the entire ~/.m2/repository folder would force all your other Java projects to re-download their dependencies too. Only clear the full cache if you have no other Maven projects on your machine.

Nice Work!

Nice Work!

You just built a complete QR code attendance system in Java. From CSV import to QR generation, check-in validation, and report export, you now have a reusable tool for managing attendance at your workshops.

You've learned how to:

  • Build a Maven-based Java project with external library dependencies (ZXing 3.5.4) for QR code encoding and decoding.
  • Generate unique QR code PNG images from a CSV file and decode them back into attendee IDs using ZXing's MultiFormatReader and QRCodeWriter.
  • Track attendance with HashMap-based duplicate prevention, timestamp logging, and export reports to both console and CSV format.
  • Secret Mission: Added late arrival detection with a configurable time threshold that flags attendees who check in after a cutoff.

Ready to quiz yourself?