Build a Flashcard Quiz App with Java

Build a full-stack flashcard app with a Java backend and jQuery frontend.

Introduction

30 Second Summary

Every student has crammed for a test using flashcards. What if you could build your own digital version that quizzes you and tracks your score?

In this project, you will build a full-stack Student Flashcard Quiz App with a Java backend and a jQuery frontend. Your app will serve flashcard data through a REST API, let you flip cards to study, and test your knowledge with a scored quiz.

What You'll Build

A Java HTTP server that serves flashcard data as JSON, connected to an interactive HTML/CSS/jQuery frontend with study and quiz modes.

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

  • A Java HTTP server with REST API endpoints that serve flashcard data as JSON.
  • Java model classes for flashcards and decks that Gson automatically converts to JSON.
  • A styled, interactive frontend built with HTML, CSS, and jQuery that loads data from your server using AJAX requests.
  • Secret Mission: Add a "Create Flashcard" form that sends new cards to the server using a POST request.

Are there any prerequisites?

This project assumes basic knowledge of Java (classes, methods, interfaces), HTML, CSS, JavaScript, and jQuery. You should also have IntelliJ IDEA and a JDK installed on your machine.

Before We Start

Before We Start...

Set Up Your Project in IntelliJ IDEA

Before you can build the flashcard app, you need a project workspace. This workspace holds both your Java backend code and your web frontend files.

This step gets your workspace ready in IntelliJ IDEA. By the end, you'll have a clean project with all the tools and folders you need.

In this step, get ready to:
  • Create a new Java project called FlashcardQuiz.
  • Add the Gson library for JSON handling.
  • Set up the flashcard package and web directory.
Create a new Java project
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type IntelliJ IDEA and press Enter to open it.
  • Click New Project on the Welcome screen.

Already have a project open?

If IntelliJ opens an existing project instead of the Welcome screen, click File in the top menu bar, then click New and select Project.

The New Project dialog has several fields to configure.

  • Enter FlashcardQuiz in the Name field.
  • Select Java as the language.
  • Select IntelliJ as the build system.
  • Verify that your JDK is selected in the JDK dropdown.
  • Click Create.

IntelliJ creates the project and opens it in the editor. You should see a Project panel on the left with a src folder.

Why IntelliJ as the build system?

The IntelliJ build system is the simplest option for this project. Maven and Gradle are more powerful, but they add configuration files and complexity you don't need here.

No JDK in the dropdown?

If the JDK dropdown is empty, click the dropdown and select Download JDK. Pick any recent version (17 or newer) and click Download. IntelliJ installs it for you automatically.

Help me set up a JDK in IntelliJ IDEA

Add the Gson library

Your server needs a way to convert Java objects into JSON so the frontend can understand the data. Gson is a library by Google that handles this conversion with just a single method call.

  • Click File in the top menu bar.
  • Click Project Structure.
  • In the left panel, click Libraries under Project Settings.
  • Click the + button at the top of the center panel.
  • Select From Maven... from the dropdown.
  • Type com.google.code.gson:gson:2.13.2 in the search box.
  • Click the search icon (magnifying glass) to search.
  • Select the result and click OK.
  • When prompted to choose a module, select your project module and click OK.
  • Click Apply.
  • Click OK to close the Project Structure window.

What is Gson?

Gson is a Java library created by Google. It converts Java objects to JSON strings (with toJson()) and JSON strings back to Java objects (with fromJson()).

JSON is the universal data format for web APIs. When your Java server sends flashcard data to the browser, it sends it as JSON.

Why not Maven or Gradle for this?

IntelliJ can download libraries directly from Maven repositories even when your project doesn't use Maven as a build tool. This is the simplest way to add a single dependency without learning a whole build system.

Can't find the Gson library?

Double-check that you typed the full Maven coordinate exactly: com.google.code.gson:gson:2.13.2. A missing character will return no results.

Make sure your computer is connected to the internet. IntelliJ needs to download the library from a remote repository.

Help me add the Gson library in IntelliJ IDEA

Create the folder structure

Your project needs two separate areas: a Java package for your backend code, and a directory for your frontend files.

  • In the Project panel on the left, right-click the src folder.
  • Select New, then Package.
  • Name the package flashcard and press Enter.
  • Right-click the project root folder (FlashcardQuiz) in the Project panel.
  • Select New, then Directory.
  • Name the directory web and press Enter.

Your project structure should now look like this:

FlashcardQuiz/
  src/
    flashcard/       (Java package for backend code)
  web/               (directory for HTML, CSS, and JS files)

What are these folders for?

The flashcard package holds all your Java server code. Every Java class you create in this project goes here.

The web directory holds your frontend files. Your HTML, CSS, and JavaScript files go here, and the Java server delivers them to the browser.

Don't see both folders?

Make sure flashcard is nested inside src, not at the project root. If it's in the wrong place, drag it into src in the Project panel.

The web directory should be at the same level as src, not inside it. If you accidentally created it inside src, drag it up to the FlashcardQuiz root.

Help me set up the correct folder structure in IntelliJ

Your project is set up and ready to go. Next up, you'll create the Java classes that represent your flashcard data.

Build the Flashcard Data Model

Your project is set up with IntelliJ IDEA, Gson, and a clean folder structure. Now it's time to define the data your app will work with.

Every flashcard has a question and an answer. Every deck groups flashcards under a name. In this step, you'll create Java classes that represent these two concepts as POJOs (Plain Old Java Objects) that Gson will later convert to JSON automatically.

In this step, get ready to:
  • Create a Flashcard class with question and answer fields.
  • Create a FlashcardDeck class that holds a collection of flashcards.
Create the Flashcard class

This class represents a single flashcard with a question and an answer. It uses private fields with getter methods, which is standard Java encapsulation.

  • In the Project panel, right-click the flashcard package under src.
  • Select New, then Java Class.
  • Name it Flashcard and press Enter.
  • Replace everything in the file with the following code:
package flashcard;

public class Flashcard {
    // Each flashcard stores a question and its answer
    private String question;
    private String answer;

    // Constructor requires both a question and answer
    public Flashcard(String question, String answer) {
        this.question = question;
        this.answer = answer;
    }

    // Getter methods let other classes read the fields
    public String getQuestion() {
        return question;
    }

    public String getAnswer() {
        return answer;
    }
}

What does this code do?

  • question and answer are private fields. Other classes can't modify them directly.
  • The constructor takes both values so every flashcard is always created with a complete question-answer pair.
  • getQuestion() and getAnswer() are public getter methods that provide read-only access to the fields. This pattern is called encapsulation.
  • Press Cmd+S (macOS) or Ctrl+S (Windows) to save Flashcard.java.
  • Check the bottom of the IntelliJ editor for error indicators. You should see no red underlines in the code, which means it compiles cleanly.

Seeing red underlines?

  • Make sure the first line says package flashcard;. This must match the package folder name exactly.
  • Make sure the class name Flashcard matches the filename Flashcard.java. Java requires these to be identical, including capitalization.
  • Still stuck? Help me fix red underlines in Flashcard.java

✔️ Awesome, I've got everything!

Great. Double check you've saved your file, then move on to the next class.

ⓧ I'd like to double check the full code

Here is the complete Flashcard.java file. Compare it with yours line by line.

package flashcard;

public class Flashcard {
    private String question;
    private String answer;

    public Flashcard(String question, String answer) {
        this.question = question;
        this.answer = answer;
    }

    public String getQuestion() {
        return question;
    }

    public String getAnswer() {
        return answer;
    }
}
Create the FlashcardDeck class

This class represents a collection of flashcards grouped under a name. It has an id, a name, and a List of Flashcard objects.

  • Right-click the flashcard package in the Project panel.
  • Select New, then Java Class.
  • Name it FlashcardDeck and press Enter.
  • Replace everything in the file with the following code:
package flashcard;

import java.util.List;

public class FlashcardDeck {
    // Each deck has a unique ID, a display name, and a list of cards
    private int id;
    private String name;
    private List<Flashcard> cards;

    // Constructor requires all three fields
    public FlashcardDeck(int id, String name, List<Flashcard> cards) {
        this.id = id;
        this.name = name;
        this.cards = cards;
    }

    // Getter methods for read-only access
    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List<Flashcard> getCards() {
        return cards;
    }
}

What does this code do?

  • id gives each deck a unique identifier that the API will use to look up a specific deck.
  • name holds the deck's display name, like "Java Basics" or "HTML Fundamentals".
  • cards is a List<Flashcard> that groups multiple flashcards together under one deck.

When Gson converts these objects to JSON, it automatically uses the field names as JSON keys. A deck with id=1 and name="Java Basics" becomes a JSON object like {"id": 1, "name": "Java Basics", "cards": [...]}.

  • Press Cmd+S (macOS) or Ctrl+S (Windows) to save FlashcardDeck.java.
  • Check that there are no red underlines in the editor. The List and Flashcard types should both resolve without errors.

Seeing an error on List or Flashcard?

  • If List is red, make sure you have import java.util.List; at the top of the file, right after the package declaration.
  • If Flashcard is red, make sure both files are in the same flashcard package. Both files must start with package flashcard;.
  • Still stuck? Help me fix compilation errors in FlashcardDeck.java

✔️ Awesome, I've got everything!

Great. Double check you've saved your file, then continue to the validation below.

ⓧ I'd like to double check the full code

Here is the complete FlashcardDeck.java file. Compare it with yours line by line.

package flashcard;

import java.util.List;

public class FlashcardDeck {
    private int id;
    private String name;
    private List<Flashcard> cards;

    public FlashcardDeck(int id, String name, List<Flashcard> cards) {
        this.id = id;
        this.name = name;
        this.cards = cards;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List<Flashcard> getCards() {
        return cards;
    }
}

Your data model is ready. You have two clean Java classes that represent every piece of data in the app. Next up, you'll build the HTTP server that turns these objects into a real API.

Build the Java HTTP Server

Your data model classes can represent flashcards and decks in Java, but they have no way to reach the browser yet. To connect a backend to a frontend, you need something that listens for requests and sends back data.

In this step, you'll create an HTTP server in Java that serves flashcard data as JSON through REST API endpoints. You'll also add a static file handler so the server can deliver your frontend files to the browser later.

In this step, get ready to:
  • Create FlashcardServer.java with sample flashcard data.
  • Add API endpoints and a static file handler.
  • Run and test the server in Chrome.
Create FlashcardServer.java with the server skeleton

Java has a built-in HTTP server class called HttpServer that can listen for web requests on a port and route them to handler methods. This is all you need to build a working backend with zero frameworks.

  • Right-click the flashcard package in the Project panel.
  • Select New > Java Class.
  • Name it FlashcardServer and press Enter.
  • Replace the entire contents of the file with the following code:
package flashcard;

import com.google.gson.Gson;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class FlashcardServer {

    // In-memory storage for all flashcard decks
    private static final List<FlashcardDeck> decks = new ArrayList<>();
    // Gson instance to convert Java objects to JSON
    private static final Gson gson = new Gson();

    public static void main(String[] args) throws IOException {
        // Populate sample flashcard data
        loadSampleData();

        // Create the HTTP server on port 8080
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        // Map URL paths to handler methods
        server.createContext("/api/decks", FlashcardServer::handleDecks);
        server.createContext("/", FlashcardServer::handleStaticFiles);
        // Use the default single-thread executor
        server.setExecutor(null);
        server.start();

        System.out.println("Server running at http://localhost:8080");
    }
}

What does this code do?

The two static final fields at the top are shared across the whole class. decks stores all flashcard decks in memory, and gson is your Gson instance for converting Java objects to JSON.

The main method creates the server on port 8080 and registers two URL paths. /api/decks routes to a handler for API requests. / routes to a handler for serving frontend files.

Red underlines are expected

IntelliJ will show red underlines for loadSampleData, handleDecks, and handleStaticFiles because those methods don't exist yet. They'll go away as you add each method below.

Now your server has a skeleton, but it references three methods that don't exist yet. The next chunk adds the first one: the sample data.

  • In FlashcardServer.java, place your cursor after the closing brace of the main method, but still inside the class body.
  • Add the loadSampleData method by pasting the following code:
    private static void loadSampleData() {
        // Java Basics deck - 5 cards
        List<Flashcard> javaCards = List.of(
            new Flashcard("What is a variable in Java?",
                "A container that holds a value in memory, declared with a type and name."),
            new Flashcard("What does 'public static void main' mean?",
                "The entry point of a Java program. 'public' = accessible everywhere, 'static' = no object needed, 'void' = no return value."),
            new Flashcard("What is the difference between == and .equals()?",
                "== compares memory references, .equals() compares the actual content of objects."),
            new Flashcard("What is an array?",
                "A fixed-size collection of elements of the same data type, accessed by index starting at 0."),
            new Flashcard("What is inheritance?",
                "A mechanism where a child class acquires the properties and methods of a parent class using 'extends'.")
        );

        // HTML Fundamentals deck - 5 cards
        List<Flashcard> htmlCards = List.of(
            new Flashcard("What does HTML stand for?",
                "HyperText Markup Language."),
            new Flashcard("What is the purpose of the <head> tag?",
                "It contains metadata, links to stylesheets, and the page title. It is not displayed on the page."),
            new Flashcard("What is the difference between <div> and <span>?",
                "<div> is a block-level element that takes full width. <span> is an inline element."),
            new Flashcard("What does the 'alt' attribute do in an <img> tag?",
                "It provides alternative text if the image cannot be displayed, and improves accessibility."),
            new Flashcard("What is semantic HTML?",
                "Using HTML elements that describe their meaning, like <header>, <nav>, <article>, and <footer>.")
        );

        // CSS Essentials deck - 5 cards
        List<Flashcard> cssCards = List.of(
            new Flashcard("What does CSS stand for?",
                "Cascading Style Sheets."),
            new Flashcard("What is the box model?",
                "Every HTML element is a box with content, padding, border, and margin."),
            new Flashcard("What is the difference between class and ID selectors?",
                "Class selectors (.) can be reused on many elements. ID selectors (#) should be unique per page."),
            new Flashcard("What does 'display: flex' do?",
                "It enables Flexbox layout, allowing child elements to be aligned and distributed in a container."),
            new Flashcard("What is specificity in CSS?",
                "A ranking system that determines which CSS rule wins. Inline styles > IDs > classes > elements.")
        );

        // Add all three decks to the in-memory list
        decks.add(new FlashcardDeck(1, "Java Basics", javaCards));
        decks.add(new FlashcardDeck(2, "HTML Fundamentals", htmlCards));
        decks.add(new FlashcardDeck(3, "CSS Essentials", cssCards));
    }

What does this code do?

This method creates three flashcard decks, each with 5 question-and-answer pairs. List.of() is a Java shortcut for creating an immutable list without manually calling add() on each item.

Each Flashcard uses the constructor you built in the previous step. Each FlashcardDeck gets an id, a name, and the list of cards. When Gson converts these objects to JSON, it automatically maps each field name to a JSON key.

  • Save FlashcardServer.java.

You won't see a change yet because the server still needs its handler methods before it can compile.

Add the API and static file handlers

The server needs two types of handlers. An API handler responds to requests with JSON data. A static file handler serves your HTML, CSS, and JS files from the web/ folder.

  • In FlashcardServer.java, place your cursor after the closing brace of the loadSampleData method.
  • Add the handleDecks method by pasting the following code:
    private static void handleDecks(HttpExchange exchange) throws IOException {
        // Only allow GET requests to this endpoint
        if (!"GET".equals(exchange.getRequestMethod())) {
            sendResponse(exchange, 405, "{\"error\": \"Method not allowed\"}");
            return;
        }

        String path = exchange.getRequestURI().getPath();
        exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");

        // If the path is exactly /api/decks, return all decks
        if (path.equals("/api/decks") || path.equals("/api/decks/")) {
            String json = gson.toJson(decks);
            sendResponse(exchange, 200, json);
        } else {
            // Otherwise, extract the deck ID from the URL
            String idStr = path.substring("/api/decks/".length());
            try {
                int id = Integer.parseInt(idStr);
                FlashcardDeck found = null;
                // Search for the deck with the matching ID
                for (FlashcardDeck deck : decks) {
                    if (deck.getId() == id) {
                        found = deck;
                        break;
                    }
                }
                if (found != null) {
                    sendResponse(exchange, 200, gson.toJson(found));
                } else {
                    sendResponse(exchange, 404, "{\"error\": \"Deck not found\"}");
                }
            } catch (NumberFormatException e) {
                sendResponse(exchange, 400, "{\"error\": \"Invalid deck ID\"}");
            }
        }
    }

What does this code do?

This is the API handler for the /api/decks endpoint. It first checks that the request uses the GET method. Any other method gets a 405 (Method Not Allowed) response.

If the URL is /api/decks, it returns all decks as JSON using gson.toJson(). If the URL includes an ID like /api/decks/1, it searches for that specific deck and returns it, or a 404 if not found.

What is a REST API?

A REST API is a set of URL endpoints that a client (like a browser) can call to get or send data. Each endpoint represents a resource. /api/decks represents all decks, and /api/decks/1 represents deck number 1.

The HTTP method tells the server what to do. GET means "give me the data." POST means "here is new data to save." This pattern is how almost every web application communicates.

  • Save FlashcardServer.java.

You won't see a change yet. The next chunk adds the static file handler.

  • Below the handleDecks method, add the handleStaticFiles method by pasting the following code:
    private static void handleStaticFiles(HttpExchange exchange) throws IOException {
        String path = exchange.getRequestURI().getPath();
        // Default to index.html when the root URL is requested
        if (path.equals("/")) {
            path = "/index.html";
        }

        // Resolve the file path relative to the web/ directory
        Path filePath = Path.of("web" + path).normalize();
        Path webRoot = Path.of("web").normalize();

        // Security check: make sure the file is inside web/ and exists
        if (!filePath.startsWith(webRoot) || !Files.exists(filePath) || Files.isDirectory(filePath)) {
            sendResponse(exchange, 404, "File not found");
            return;
        }

        // Set the correct Content-Type based on file extension
        String contentType = "text/html; charset=utf-8";
        if (path.endsWith(".css")) {
            contentType = "text/css; charset=utf-8";
        } else if (path.endsWith(".js")) {
            contentType = "application/javascript; charset=utf-8";
        }

        // Read the file and send it to the browser
        byte[] fileBytes = Files.readAllBytes(filePath);
        exchange.getResponseHeaders().set("Content-Type", contentType);
        exchange.sendResponseHeaders(200, fileBytes.length);
        OutputStream os = exchange.getResponseBody();
        os.write(fileBytes);
        os.close();
    }

What does this code do?

This handler serves your frontend files (HTML, CSS, JS) from the web/ directory. When someone visits http://localhost:8080, the handler translates / into /index.html automatically.

The normalize() and startsWith() checks are a security guard. They prevent a malicious request from reading files outside the web/ folder using tricks like ../../etc/passwords.

  • Save FlashcardServer.java.

You won't see a change yet. The final piece is the sendResponse helper that both handlers depend on.

  • Below the handleStaticFiles method, add the sendResponse helper by pasting the following code:
    private static void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException {
        // Convert the response string to bytes for accurate Content-Length
        byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
        exchange.sendResponseHeaders(statusCode, bytes.length);
        OutputStream os = exchange.getResponseBody();
        os.write(bytes);
        os.close();
    }

What does this code do?

This is a utility method that both handleDecks and handleStaticFiles use to send responses back to the browser. It converts the response string to bytes using UTF-8 encoding, sets the HTTP status code, and writes the bytes to the output stream.

  • Save FlashcardServer.java.

All the red underlines should disappear now. Your file has every method the server needs.

Still seeing red underlines?

  • Make sure every method is inside the FlashcardServer class body (between the class opening brace and the final closing brace).
  • Check that the Gson library is still added. Go to File > Project Structure > Libraries and confirm gson-2.13.2 is listed.
  • Verify the class is in the flashcard package. The first line should say package flashcard;.

Help me fix red underlines in FlashcardServer.java

✔️ Awesome, I've got everything!

Great. Double check you've saved FlashcardServer.java before moving on.

ⓧ I'd like to double check the full code

package flashcard;

import com.google.gson.Gson;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class FlashcardServer {

    private static final List<FlashcardDeck> decks = new ArrayList<>();
    private static final Gson gson = new Gson();

    public static void main(String[] args) throws IOException {
        loadSampleData();

        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/api/decks", FlashcardServer::handleDecks);
        server.createContext("/", FlashcardServer::handleStaticFiles);
        server.setExecutor(null);
        server.start();

        System.out.println("Server running at http://localhost:8080");
    }

    private static void loadSampleData() {
        List<Flashcard> javaCards = List.of(
            new Flashcard("What is a variable in Java?",
                "A container that holds a value in memory, declared with a type and name."),
            new Flashcard("What does 'public static void main' mean?",
                "The entry point of a Java program. 'public' = accessible everywhere, 'static' = no object needed, 'void' = no return value."),
            new Flashcard("What is the difference between == and .equals()?",
                "== compares memory references, .equals() compares the actual content of objects."),
            new Flashcard("What is an array?",
                "A fixed-size collection of elements of the same data type, accessed by index starting at 0."),
            new Flashcard("What is inheritance?",
                "A mechanism where a child class acquires the properties and methods of a parent class using 'extends'.")
        );

        List<Flashcard> htmlCards = List.of(
            new Flashcard("What does HTML stand for?",
                "HyperText Markup Language."),
            new Flashcard("What is the purpose of the <head> tag?",
                "It contains metadata, links to stylesheets, and the page title. It is not displayed on the page."),
            new Flashcard("What is the difference between <div> and <span>?",
                "<div> is a block-level element that takes full width. <span> is an inline element."),
            new Flashcard("What does the 'alt' attribute do in an <img> tag?",
                "It provides alternative text if the image cannot be displayed, and improves accessibility."),
            new Flashcard("What is semantic HTML?",
                "Using HTML elements that describe their meaning, like <header>, <nav>, <article>, and <footer>.")
        );

        List<Flashcard> cssCards = List.of(
            new Flashcard("What does CSS stand for?",
                "Cascading Style Sheets."),
            new Flashcard("What is the box model?",
                "Every HTML element is a box with content, padding, border, and margin."),
            new Flashcard("What is the difference between class and ID selectors?",
                "Class selectors (.) can be reused on many elements. ID selectors (#) should be unique per page."),
            new Flashcard("What does 'display: flex' do?",
                "It enables Flexbox layout, allowing child elements to be aligned and distributed in a container."),
            new Flashcard("What is specificity in CSS?",
                "A ranking system that determines which CSS rule wins. Inline styles > IDs > classes > elements.")
        );

        decks.add(new FlashcardDeck(1, "Java Basics", javaCards));
        decks.add(new FlashcardDeck(2, "HTML Fundamentals", htmlCards));
        decks.add(new FlashcardDeck(3, "CSS Essentials", cssCards));
    }

    private static void handleDecks(HttpExchange exchange) throws IOException {
        if (!"GET".equals(exchange.getRequestMethod())) {
            sendResponse(exchange, 405, "{\"error\": \"Method not allowed\"}");
            return;
        }

        String path = exchange.getRequestURI().getPath();
        exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");

        if (path.equals("/api/decks") || path.equals("/api/decks/")) {
            String json = gson.toJson(decks);
            sendResponse(exchange, 200, json);
        } else {
            String idStr = path.substring("/api/decks/".length());
            try {
                int id = Integer.parseInt(idStr);
                FlashcardDeck found = null;
                for (FlashcardDeck deck : decks) {
                    if (deck.getId() == id) {
                        found = deck;
                        break;
                    }
                }
                if (found != null) {
                    sendResponse(exchange, 200, gson.toJson(found));
                } else {
                    sendResponse(exchange, 404, "{\"error\": \"Deck not found\"}");
                }
            } catch (NumberFormatException e) {
                sendResponse(exchange, 400, "{\"error\": \"Invalid deck ID\"}");
            }
        }
    }

    private static void handleStaticFiles(HttpExchange exchange) throws IOException {
        String path = exchange.getRequestURI().getPath();
        if (path.equals("/")) {
            path = "/index.html";
        }

        Path filePath = Path.of("web" + path).normalize();
        Path webRoot = Path.of("web").normalize();

        if (!filePath.startsWith(webRoot) || !Files.exists(filePath) || Files.isDirectory(filePath)) {
            sendResponse(exchange, 404, "File not found");
            return;
        }

        String contentType = "text/html; charset=utf-8";
        if (path.endsWith(".css")) {
            contentType = "text/css; charset=utf-8";
        } else if (path.endsWith(".js")) {
            contentType = "application/javascript; charset=utf-8";
        }

        byte[] fileBytes = Files.readAllBytes(filePath);
        exchange.getResponseHeaders().set("Content-Type", contentType);
        exchange.sendResponseHeaders(200, fileBytes.length);
        OutputStream os = exchange.getResponseBody();
        os.write(fileBytes);
        os.close();
    }

    private static void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException {
        byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
        exchange.sendResponseHeaders(statusCode, bytes.length);
        OutputStream os = exchange.getResponseBody();
        os.write(bytes);
        os.close();
    }
}

Why use Java's built-in HttpServer instead of a framework?

Java's com.sun.net.httpserver.HttpServer is built into the JDK. It requires zero external dependencies and teaches you the core HTTP concepts (ports, paths, handlers, status codes) that transfer directly to any framework later.

Frameworks like Spring Boot add convenience on top of these same concepts. Learning the fundamentals first means you'll understand what the framework is doing for you.

Run and test the server
  • Right-click FlashcardServer.java in the Project panel.
  • Select Run 'FlashcardServer.main()'.

You should see Server running at http://localhost:8080 in the IntelliJ console at the bottom of the window.

  • Open Google Chrome.
  • Type http://localhost:8080/api/decks in the address bar and press Enter.

You should see a wall of JSON text containing all three flashcard decks with their questions and answers.

  • Now try http://localhost:8080/api/decks/1 in the address bar.

You should see just the Java Basics deck with its 5 flashcards.

Don't see JSON in the browser?

  • Check the IntelliJ console for error messages. A BindException means port 8080 is already in use by another program. Close that program and try again.
  • Make sure the console says Server running at http://localhost:8080. If you see a compilation error instead, double check your code against the full file in the tab above.
  • Verify the URL is exactly http://localhost:8080/api/decks with no extra spaces or characters.

Help me troubleshoot my Java HTTP server not responding in the browser

Leave the server running

Keep the server running in IntelliJ as you move to the next steps. You'll need it active to test the frontend you build next.

Your Java backend is live and serving data. Next up, you'll build the HTML and CSS frontend that turns this raw JSON into a polished flashcard interface.

Build the Frontend Interface

Your Java backend is running and serving flashcard data as JSON through your API endpoints. Now you need a frontend that displays that data in the browser.

In this step, you'll create the HTML structure and CSS styles for the flashcard app. The page has three sections: a deck list, a study mode with card flipping, and a quiz mode.

In this step, get ready to:
  • Build the HTML page with deck list, study mode, and quiz mode sections.
  • Style the page with CSS including a gradient header, card flip animations, and quiz feedback colors.
Create the HTML structure

Your web/ directory is currently empty. The handleStaticFiles method you built in the previous step already knows how to serve files from this folder. You just need to create the files.

  • In the Project panel on the left side of IntelliJ, right-click the web folder.
  • Select New, then File.
  • Name the file index.html and press Enter.
  • Paste the following HTML into the new file:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Student Flashcard Quiz</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <!-- Header with app title and subtitle -->
    <header>
        <h1>📚 Flashcard Quiz</h1>
        <p>Pick a deck, study the cards, then test yourself!</p>
    </header>

    <main>
        <!-- Section 1: Deck selection list -->
        <section id="deck-list">
            <h2>Choose a Deck</h2>
            <div id="decks-container"></div>
        </section>

        <!-- Section 2: Study mode (hidden until a deck is selected) -->
        <section id="study-mode" class="hidden">
            <div id="study-header">
                <button id="back-to-decks">&larr; Back to Decks</button>
                <h2 id="deck-title"></h2>
                <span id="card-counter"></span>
            </div>
            <div id="flashcard" class="card">
                <div class="card-inner">
                    <div class="card-front">
                        <p id="card-question"></p>
                    </div>
                    <div class="card-back">
                        <p id="card-answer"></p>
                    </div>
                </div>
            </div>
            <div id="card-controls">
                <button id="prev-card">&larr; Previous</button>
                <button id="flip-card">Flip Card</button>
                <button id="next-card">Next &rarr;</button>
            </div>
            <button id="start-quiz" class="quiz-btn">Start Quiz</button>
        </section>

        <!-- Section 3: Quiz mode (hidden until quiz starts) -->
        <section id="quiz-mode" class="hidden">
            <div id="quiz-header">
                <h2>Quiz Mode</h2>
                <span id="quiz-score"></span>
            </div>
            <div id="quiz-card" class="card quiz">
                <p id="quiz-question"></p>
            </div>
            <div id="quiz-options"></div>
            <div id="quiz-result" class="hidden">
                <h2 id="result-title"></h2>
                <p id="result-message"></p>
                <button id="restart-quiz">Try Again</button>
                <button id="back-to-study">Back to Study</button>
            </div>
        </section>
    </main>

    <!-- Load jQuery from CDN, then your app script -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="app.js"></script>
</body>
</html>

What does this HTML do?

The page is split into three <section> elements inside <main>. Only one section is visible at a time.

  • #deck-list is the landing page. The empty #decks-container div is where jQuery will inject clickable deck cards later.
  • #study-mode has a flashcard with front and back faces, plus navigation buttons. It starts hidden with the hidden class.
  • #quiz-mode holds the quiz question, multiple-choice options, and a results panel. It also starts hidden.
  • jQuery 3.7.1 loads from a CDN at the bottom of the body, followed by your own app.js file (which you'll create in the next step).
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Open Chrome and go to http://localhost:8080. You should see the unstyled text "Flashcard Quiz", "Choose a Deck", and button labels. The page looks plain because the stylesheet doesn't exist yet.

Seeing "File not found" in the browser?

  • Make sure the file is saved inside the web folder at the project root, not inside src.
  • Confirm the file is named exactly index.html (all lowercase).
  • Check that the Java server is still running in the IntelliJ console. If it stopped, right-click FlashcardServer.java and select Run again.

Still stuck? Ask for help.

✔️ Awesome, I've got everything!

Make sure you've saved index.html before moving on to the styles.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Student Flashcard Quiz</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <h1>📚 Flashcard Quiz</h1>
        <p>Pick a deck, study the cards, then test yourself!</p>
    </header>

    <main>
        <section id="deck-list">
            <h2>Choose a Deck</h2>
            <div id="decks-container"></div>
        </section>

        <section id="study-mode" class="hidden">
            <div id="study-header">
                <button id="back-to-decks">&larr; Back to Decks</button>
                <h2 id="deck-title"></h2>
                <span id="card-counter"></span>
            </div>
            <div id="flashcard" class="card">
                <div class="card-inner">
                    <div class="card-front">
                        <p id="card-question"></p>
                    </div>
                    <div class="card-back">
                        <p id="card-answer"></p>
                    </div>
                </div>
            </div>
            <div id="card-controls">
                <button id="prev-card">&larr; Previous</button>
                <button id="flip-card">Flip Card</button>
                <button id="next-card">Next &rarr;</button>
            </div>
            <button id="start-quiz" class="quiz-btn">Start Quiz</button>
        </section>

        <section id="quiz-mode" class="hidden">
            <div id="quiz-header">
                <h2>Quiz Mode</h2>
                <span id="quiz-score"></span>
            </div>
            <div id="quiz-card" class="card quiz">
                <p id="quiz-question"></p>
            </div>
            <div id="quiz-options"></div>
            <div id="quiz-result" class="hidden">
                <h2 id="result-title"></h2>
                <p id="result-message"></p>
                <button id="restart-quiz">Try Again</button>
                <button id="back-to-study">Back to Study</button>
            </div>
        </section>
    </main>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="app.js"></script>
</body>
</html>
Create the page styles

The CSS file styles all three sections of the page: the deck list, the study mode flashcard, and the quiz. It also handles the card flip effect by toggling visibility of the front and back faces.

  • Right-click the web folder in the Project panel.
  • Select New, then File.
  • Name the file style.css and press Enter.
  • Add the page foundation and deck list styles by pasting this code into style.css:
/* Reset default browser spacing */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f4f8;
    color: #333;
    min-height: 100vh;
}

/* Gradient header bar */
header {
    background: linear-gradient(135deg, #667eea, #764ba2);
    color: white;
    text-align: center;
    padding: 30px 20px;
}

header h1 {
    font-size: 2rem;
    margin-bottom: 8px;
}

header p {
    font-size: 1.1rem;
    opacity: 0.9;
}

/* Center the main content area */
main {
    max-width: 700px;
    margin: 30px auto;
    padding: 0 20px;
}

/* Utility class to hide sections until jQuery shows them */
.hidden {
    display: none;
}

/* Deck List */
#decks-container {
    display: flex;
    flex-direction: column;
    gap: 15px;
    margin-top: 15px;
}

/* Each deck appears as a clickable card */
.deck-item {
    background: white;
    border-radius: 12px;
    padding: 20px 25px;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    transition: transform 0.2s, box-shadow 0.2s;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* Lift effect on hover */
.deck-item:hover {
    transform: translateY(-3px);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.deck-item h3 {
    font-size: 1.2rem;
    color: #333;
}

/* Card count badge */
.deck-item span {
    background: #667eea;
    color: white;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.85rem;
}

What does this CSS do?

  • The * reset removes default browser margins and padding so everything starts from a clean slate.
  • The header uses a purple gradient background (linear-gradient) that blends two colors diagonally.
  • The .hidden utility class sets display: none. jQuery will add and remove this class to show and hide sections.
  • Each .deck-item has a hover animation that lifts the card upward with translateY(-3px) and deepens the shadow.
  • Save style.css and refresh http://localhost:8080 in Chrome.
  • You should see a purple gradient header with the title "Flashcard Quiz" and the subtitle text below it. The "Choose a Deck" heading appears below the header on a light gray background.

Page still looks unstyled?

  • Check that the file is named style.css (not styles.css). The HTML <link> tag references style.css exactly.
  • Make sure style.css is inside the web folder alongside index.html.
  • Try a hard refresh with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to bypass the browser cache.

Still stuck? Ask for help.

  • Add the study mode styles below the deck-item styles you just wrote:
/* Study Mode */
#study-header {
    display: flex;
    align-items: center;
    gap: 15px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}

#study-header h2 {
    flex: 1;
}

/* Card counter badge */
#card-counter {
    background: #667eea;
    color: white;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.9rem;
}

/* Flashcard container */
.card {
    background: white;
    border-radius: 16px;
    min-height: 250px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
    padding: 30px;
    text-align: center;
    cursor: pointer;
    position: relative;
}

.card .card-inner {
    width: 100%;
}

.card-front p,
.card-back p {
    font-size: 1.2rem;
    line-height: 1.6;
}

/* Card back is hidden by default */
.card-back {
    display: none;
    color: #667eea;
    font-weight: 600;
}

/* When .flipped is added, hide front and show back */
.card.flipped .card-front {
    display: none;
}

.card.flipped .card-back {
    display: block;
}

/* Navigation buttons row */
#card-controls {
    display: flex;
    justify-content: center;
    gap: 12px;
    margin-top: 20px;
}

/* Base button styles shared by all buttons */
button {
    padding: 10px 20px;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    font-size: 0.95rem;
    font-weight: 600;
    transition: background-color 0.2s;
}

#back-to-decks {
    background: #e2e8f0;
    color: #333;
}

#back-to-decks:hover {
    background: #cbd5e1;
}

#prev-card,
#next-card {
    background: #e2e8f0;
    color: #333;
}

#prev-card:hover,
#next-card:hover {
    background: #cbd5e1;
}

/* Flip button uses the accent color */
#flip-card {
    background: #667eea;
    color: white;
}

#flip-card:hover {
    background: #5a6fd6;
}

/* Start Quiz button */
.quiz-btn {
    display: block;
    margin: 25px auto 0;
    background: #48bb78;
    color: white;
    padding: 12px 30px;
    font-size: 1rem;
}

.quiz-btn:hover {
    background: #38a169;
}

How does the card flip work?

The flashcard has two child divs: .card-front (the question) and .card-back (the answer). By default, the back is hidden with display: none.

When jQuery adds the .flipped class to the card, the CSS rules swap which face is visible. The front hides and the back appears. Removing the class flips it back.

  • Save style.css.
  • You won't see a change in the browser yet. The study mode section is hidden until jQuery shows it in the next step.
  • Add the quiz mode styles below the study mode styles:
/* Quiz Mode */
#quiz-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

/* Score badge */
#quiz-score {
    background: #48bb78;
    color: white;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.9rem;
}

/* Quiz card is shorter and not clickable */
.card.quiz {
    min-height: 150px;
    cursor: default;
}

/* Answer options stack vertically */
#quiz-options {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 20px;
}

.quiz-option {
    background: white;
    border: 2px solid #e2e8f0;
    border-radius: 10px;
    padding: 15px 20px;
    text-align: left;
    font-size: 1rem;
    font-weight: 500;
    transition: border-color 0.2s, background-color 0.2s;
}

.quiz-option:hover {
    border-color: #667eea;
    background: #f7f8ff;
}

/* Green highlight for the correct answer */
.quiz-option.correct {
    border-color: #48bb78;
    background: #f0fff4;
    color: #22543d;
}

/* Red highlight for a wrong selection */
.quiz-option.wrong {
    border-color: #f56565;
    background: #fff5f5;
    color: #742a2a;
}

/* Quiz Result panel */
#quiz-result {
    text-align: center;
    background: white;
    border-radius: 16px;
    padding: 40px 30px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
    margin-top: 20px;
}

#result-title {
    font-size: 1.8rem;
    margin-bottom: 10px;
}

#result-message {
    font-size: 1.1rem;
    color: #666;
    margin-bottom: 25px;
}

#restart-quiz {
    background: #667eea;
    color: white;
    margin-right: 10px;
}

#restart-quiz:hover {
    background: #5a6fd6;
}

#back-to-study {
    background: #e2e8f0;
    color: #333;
}

#back-to-study:hover {
    background: #cbd5e1;
}

What do the quiz styles do?

  • .quiz-option.correct turns the border and background green when jQuery marks an answer as correct.
  • .quiz-option.wrong turns them red for an incorrect selection. jQuery adds these classes after the learner clicks an option.
  • The #quiz-result panel is a centered white card that displays the final score and a performance message at the end of the quiz.
  • Save style.css.
  • You won't see the quiz styles in action yet. The quiz section is hidden until jQuery activates it in the next step.

✔️ Awesome, I've got everything!

Make sure you've saved style.css with all three sections of styles.

ⓧ I'd like to double check the full code

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f4f8;
    color: #333;
    min-height: 100vh;
}

header {
    background: linear-gradient(135deg, #667eea, #764ba2);
    color: white;
    text-align: center;
    padding: 30px 20px;
}

header h1 {
    font-size: 2rem;
    margin-bottom: 8px;
}

header p {
    font-size: 1.1rem;
    opacity: 0.9;
}

main {
    max-width: 700px;
    margin: 30px auto;
    padding: 0 20px;
}

.hidden {
    display: none;
}

/* Deck List */
#decks-container {
    display: flex;
    flex-direction: column;
    gap: 15px;
    margin-top: 15px;
}

.deck-item {
    background: white;
    border-radius: 12px;
    padding: 20px 25px;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    transition: transform 0.2s, box-shadow 0.2s;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.deck-item:hover {
    transform: translateY(-3px);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.deck-item h3 {
    font-size: 1.2rem;
    color: #333;
}

.deck-item span {
    background: #667eea;
    color: white;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.85rem;
}

/* Study Mode */
#study-header {
    display: flex;
    align-items: center;
    gap: 15px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}

#study-header h2 {
    flex: 1;
}

#card-counter {
    background: #667eea;
    color: white;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.9rem;
}

/* Flashcard */
.card {
    background: white;
    border-radius: 16px;
    min-height: 250px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
    padding: 30px;
    text-align: center;
    cursor: pointer;
    position: relative;
}

.card .card-inner {
    width: 100%;
}

.card-front p,
.card-back p {
    font-size: 1.2rem;
    line-height: 1.6;
}

.card-back {
    display: none;
    color: #667eea;
    font-weight: 600;
}

.card.flipped .card-front {
    display: none;
}

.card.flipped .card-back {
    display: block;
}

/* Controls */
#card-controls {
    display: flex;
    justify-content: center;
    gap: 12px;
    margin-top: 20px;
}

button {
    padding: 10px 20px;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    font-size: 0.95rem;
    font-weight: 600;
    transition: background-color 0.2s;
}

#back-to-decks {
    background: #e2e8f0;
    color: #333;
}

#back-to-decks:hover {
    background: #cbd5e1;
}

#prev-card,
#next-card {
    background: #e2e8f0;
    color: #333;
}

#prev-card:hover,
#next-card:hover {
    background: #cbd5e1;
}

#flip-card {
    background: #667eea;
    color: white;
}

#flip-card:hover {
    background: #5a6fd6;
}

.quiz-btn {
    display: block;
    margin: 25px auto 0;
    background: #48bb78;
    color: white;
    padding: 12px 30px;
    font-size: 1rem;
}

.quiz-btn:hover {
    background: #38a169;
}

/* Quiz Mode */
#quiz-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

#quiz-score {
    background: #48bb78;
    color: white;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.9rem;
}

.card.quiz {
    min-height: 150px;
    cursor: default;
}

#quiz-options {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 20px;
}

.quiz-option {
    background: white;
    border: 2px solid #e2e8f0;
    border-radius: 10px;
    padding: 15px 20px;
    text-align: left;
    font-size: 1rem;
    font-weight: 500;
    transition: border-color 0.2s, background-color 0.2s;
}

.quiz-option:hover {
    border-color: #667eea;
    background: #f7f8ff;
}

.quiz-option.correct {
    border-color: #48bb78;
    background: #f0fff4;
    color: #22543d;
}

.quiz-option.wrong {
    border-color: #f56565;
    background: #fff5f5;
    color: #742a2a;
}

/* Quiz Result */
#quiz-result {
    text-align: center;
    background: white;
    border-radius: 16px;
    padding: 40px 30px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
    margin-top: 20px;
}

#result-title {
    font-size: 1.8rem;
    margin-bottom: 10px;
}

#result-message {
    font-size: 1.1rem;
    color: #666;
    margin-bottom: 25px;
}

#restart-quiz {
    background: #667eea;
    color: white;
    margin-right: 10px;
}

#restart-quiz:hover {
    background: #5a6fd6;
}

#back-to-study {
    background: #e2e8f0;
    color: #333;
}

#back-to-study:hover {
    background: #cbd5e1;
}

Your HTML structure and styles are in place. The page looks great, but the deck list is empty and none of the buttons do anything yet. In the next step, you'll write the jQuery code that fetches data from your Java API and brings the entire app to life.

Add Interactivity with jQuery

Your backend is serving data and your frontend has a full HTML structure with CSS styling. The missing piece is the JavaScript that connects them.

In this step, you write the jQuery code that fetches flashcard data from your Java server using AJAX requests. You also build all the user interactions: browsing decks, flipping cards, navigating between cards, and taking a scored quiz.

In this step, get ready to:
  • Load flashcard decks from the server using jQuery AJAX.
  • Add card flipping and navigation controls for study mode.
  • Build a quiz mode with scoring and results.
Create app.js and load the deck list

When the page loads, jQuery needs to ask the Java server for the list of flashcard decks. It does this by sending a GET request to /api/decks using $.ajax(). The server responds with JSON, and jQuery builds the deck list in the browser.

  • In the Project panel, right-click the web folder.
  • Select New > File and name it app.js.
  • Paste the following code into app.js:
$(document).ready(function () {
    var currentDeck = null;
    var currentCardIndex = 0;
    var quizCards = [];
    var quizIndex = 0;
    var quizScore = 0;

    // Load all decks on page load
    loadDecks();

    function loadDecks() {
        $.ajax({
            url: "/api/decks",
            method: "GET",
            dataType: "json",
            success: function (decks) {
                $("#decks-container").empty();
                decks.forEach(function (deck) {
                    var deckItem = $(
                        '<div class="deck-item" data-id="' + deck.id + '">' +
                        "<h3>" + deck.name + "</h3>" +
                        "<span>" + deck.cards.length + " cards</span>" +
                        "</div>"
                    );
                    $("#decks-container").append(deckItem);
                });
            },
            error: function () {
                $("#decks-container").html(
                    "<p>Failed to load decks. Is the server running?</p>"
                );
            }
        });
    }
});

What does this code do?

$(document).ready() waits for the HTML page to fully load before running any JavaScript. Everything else lives inside this wrapper.

The five var lines at the top are state variables that track which deck is open, which card is showing, and quiz progress.

The loadDecks() function sends a GET request to /api/decks. When the server responds with JSON, the success callback loops through each deck and creates a clickable .deck-item element in the #decks-container div.

Why use $.ajax() instead of fetch()?

Since this project uses jQuery, $.ajax() is the jQuery-native way to make HTTP requests. It automatically parses the JSON response when you set dataType: "json". The modern fetch() API works too, but $.ajax() keeps everything consistent in one library.

  • Save app.js.

You won't see a change in the browser yet because clicking a deck calls showStudyMode(), which you haven't written yet. The next chunk adds that.

When a user clicks a deck card, the app needs to fetch that specific deck's full data from the server. Add the click handler that makes this second AJAX call.

  • In app.js, find the closing } of the loadDecks function. Add the following code below it, but still inside the $(document).ready() wrapper:
    // Click a deck to open it
    $(document).on("click", ".deck-item", function () {
        var deckId = $(this).data("id");
        $.ajax({
            url: "/api/decks/" + deckId,
            method: "GET",
            dataType: "json",
            success: function (deck) {
                currentDeck = deck;
                currentCardIndex = 0;
                showStudyMode();
            }
        });
    });

What does this code do?

$(document).on("click", ".deck-item", ...) is a delegated event handler. It listens for clicks on .deck-item elements even though they were created dynamically by loadDecks().

When clicked, it reads the deck's data-id attribute and fetches the full deck from /api/decks/{id}. The response is stored in currentDeck, then showStudyMode() switches the view.

  • Save app.js.

You still won't see study mode work because showStudyMode() and showCard() don't exist yet. The next substep adds them.

Add card flipping and navigation

Study mode displays one flashcard at a time. The learner can flip the card to reveal the answer, navigate between cards, and go back to the deck list.

  • In app.js, add the following two functions below the .deck-item click handler you just wrote:
    function showStudyMode() {
        $("#deck-list").addClass("hidden");
        $("#quiz-mode").addClass("hidden");
        $("#study-mode").removeClass("hidden");
        $("#deck-title").text(currentDeck.name);
        showCard();
    }

    function showCard() {
        var card = currentDeck.cards[currentCardIndex];
        $("#card-question").text(card.question);
        $("#card-answer").text(card.answer);
        $("#flashcard").removeClass("flipped");
        $("#card-counter").text(
            "Card " + (currentCardIndex + 1) + " of " + currentDeck.cards.length
        );
    }

What does this code do?

showStudyMode() hides the deck list and quiz sections, then shows the study section. It sets the deck title and calls showCard() to display the first card.

showCard() reads the current card from the deck array using currentCardIndex. It sets the question and answer text, removes the .flipped class (so the card always starts showing the question side), and updates the card counter.

  • Save app.js.

You won't see flipping or navigation work yet because those event handlers haven't been registered. The next chunk adds them.

Now you need to wire up the buttons. Clicking the card or the Flip Card button toggles the .flipped CSS class. The Previous and Next buttons change currentCardIndex and refresh the card.

  • Add the following event handlers below the showCard function:
    // Flip card
    $("#flip-card").on("click", function () {
        $("#flashcard").toggleClass("flipped");
    });

    $("#flashcard").on("click", function () {
        $(this).toggleClass("flipped");
    });

    // Navigate cards
    $("#next-card").on("click", function () {
        if (currentCardIndex < currentDeck.cards.length - 1) {
            currentCardIndex++;
            showCard();
        }
    });

    $("#prev-card").on("click", function () {
        if (currentCardIndex > 0) {
            currentCardIndex--;
            showCard();
        }
    });

    // Back to deck list
    $("#back-to-decks").on("click", function () {
        $("#study-mode").addClass("hidden");
        $("#deck-list").removeClass("hidden");
    });

What does this code do?

The two flip handlers use toggleClass("flipped") to add or remove the .flipped class on #flashcard. Your CSS hides the front and shows the back when this class is present.

The navigation handlers check boundary conditions before changing the index. #next-card only increments if you're not on the last card. #prev-card only decrements if you're not on the first card.

  • Save app.js.
  • If your server is still running, open Chrome and go to http://localhost:8080. You should see three deck cards. Click one to enter study mode, then flip the card and use the navigation buttons.

Don't see the deck list?

  • Make sure your Java server is still running in IntelliJ. You should see Server running at http://localhost:8080 in the console.
  • Check the browser's developer console (F12 then the Console tab) for error messages. A 404 on app.js means the file is not in the web/ folder.
  • If the server was started before you created app.js, stop and restart it so it picks up the new file.

Still stuck? Ask for help.

Add quiz mode with scoring

Quiz mode takes the cards from the current deck, shuffles them, and presents each question with three answer choices. The app tracks the score and shows a results screen at the end.

  • In app.js, add the following code below the #back-to-decks click handler:
    // Start Quiz
    $("#start-quiz").on("click", function () {
        quizCards = currentDeck.cards.slice();
        shuffleArray(quizCards);
        quizIndex = 0;
        quizScore = 0;
        $("#study-mode").addClass("hidden");
        $("#quiz-mode").removeClass("hidden");
        $("#quiz-result").addClass("hidden");
        $("#quiz-card").show();
        $("#quiz-options").show();
        showQuizQuestion();
    });

    function showQuizQuestion() {
        var card = quizCards[quizIndex];
        $("#quiz-question").text(card.question);
        $("#quiz-score").text("Score: " + quizScore + " / " + quizCards.length);

        // Generate options: correct answer + 2 wrong answers from other cards
        var options = [card.answer];
        var otherAnswers = currentDeck.cards
            .filter(function (c) {
                return c.answer !== card.answer;
            })
            .map(function (c) {
                return c.answer;
            });
        shuffleArray(otherAnswers);
        options.push(otherAnswers[0], otherAnswers[1]);
        shuffleArray(options);

        $("#quiz-options").empty();
        options.forEach(function (option) {
            var btn = $('<button class="quiz-option"></button>');
            btn.text(option);
            btn.data("answer", option);
            $("#quiz-options").append(btn);
        });
    }

What does this code do?

The #start-quiz handler copies the current deck's cards into quizCards using .slice() (so the original array stays untouched), shuffles them, resets the score, and switches to quiz mode.

showQuizQuestion() generates three answer options for each question: the correct answer plus two random wrong answers pulled from other cards in the same deck. All three options are shuffled so the correct answer is not always in the same position.

  • Save app.js.

You won't be able to answer quiz questions yet because the click handler for quiz options hasn't been added. The next chunk handles that.

When a user clicks an answer option, the app needs to check if it's correct, highlight the right answer in green, mark wrong selections in red, and advance to the next question after a short delay.

  • Add the following click handler below the showQuizQuestion function:
    // Handle quiz answer
    $(document).on("click", ".quiz-option", function () {
        if ($(this).hasClass("correct") || $(this).hasClass("wrong")) {
            return;
        }

        var selected = $(this).data("answer");
        var correct = quizCards[quizIndex].answer;

        // Highlight the correct answer
        $(".quiz-option").each(function () {
            if ($(this).data("answer") === correct) {
                $(this).addClass("correct");
            }
        });

        if (selected === correct) {
            quizScore++;
            $(this).addClass("correct");
        } else {
            $(this).addClass("wrong");
        }

        $("#quiz-score").text("Score: " + quizScore + " / " + quizCards.length);

        // Disable all options after answering
        $(".quiz-option").css("pointer-events", "none");

        // Move to next question after a short delay
        setTimeout(function () {
            quizIndex++;
            if (quizIndex < quizCards.length) {
                $(".quiz-option").css("pointer-events", "auto");
                showQuizQuestion();
            } else {
                showQuizResult();
            }
        }, 1200);
    });

What does this code do?

The first if check prevents double-clicking by returning early if the button is already marked as correct or wrong.

After a click, the handler always highlights the correct answer in green using the .correct CSS class. If the user picked wrong, their selection also gets the .wrong class (red). After 1200 milliseconds, the quiz advances to the next question or shows the results.

  • Save app.js.

The quiz can now accept answers, but it still needs the results screen, the restart button, and the shuffle utility. The final chunk adds all of these.

  • Add the following code below the .quiz-option click handler to complete app.js:
    function showQuizResult() {
        $("#quiz-card").hide();
        $("#quiz-options").hide();
        $("#quiz-result").removeClass("hidden");

        var percentage = Math.round((quizScore / quizCards.length) * 100);
        if (percentage === 100) {
            $("#result-title").text("Perfect Score!");
            $("#result-message").text(
                "You got " + quizScore + " out of " + quizCards.length +
                " correct. Amazing work!"
            );
        } else if (percentage >= 60) {
            $("#result-title").text("Nice Job!");
            $("#result-message").text(
                "You got " + quizScore + " out of " + quizCards.length +
                " correct. Keep studying!"
            );
        } else {
            $("#result-title").text("Keep Practicing!");
            $("#result-message").text(
                "You got " + quizScore + " out of " + quizCards.length +
                " correct. Review the cards and try again."
            );
        }
    }

    // Restart quiz
    $("#restart-quiz").on("click", function () {
        quizCards = currentDeck.cards.slice();
        shuffleArray(quizCards);
        quizIndex = 0;
        quizScore = 0;
        $("#quiz-result").addClass("hidden");
        $("#quiz-card").show();
        $("#quiz-options").show();
        $(".quiz-option").css("pointer-events", "auto");
        showQuizQuestion();
    });

    // Back to study from quiz
    $("#back-to-study").on("click", function () {
        $("#quiz-mode").addClass("hidden");
        showStudyMode();
    });

    // Utility: Fisher-Yates shuffle
    function shuffleArray(array) {
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }

What does this code do?

showQuizResult() hides the quiz card and options, then shows a results panel. It calculates the score percentage and displays a different title and message based on performance: perfect (100%), passing (60%+), or needs practice (below 60%).

The #restart-quiz handler re-shuffles the cards and resets the score so the user can try again. The #back-to-study handler returns to the study mode flashcard view.

shuffleArray() uses the Fisher-Yates algorithm, which is the standard way to randomly reorder an array. It walks backwards through the array and swaps each element with a randomly chosen earlier element.

  • Save app.js.

✔️ Awesome, I've got everything!

Great. Double check you've saved app.js before moving on.

ⓧ I'd like to double check the full code

Here is the complete app.js file. Compare it to yours and fix any differences.

$(document).ready(function () {
    var currentDeck = null;
    var currentCardIndex = 0;
    var quizCards = [];
    var quizIndex = 0;
    var quizScore = 0;

    // Load all decks on page load
    loadDecks();

    function loadDecks() {
        $.ajax({
            url: "/api/decks",
            method: "GET",
            dataType: "json",
            success: function (decks) {
                $("#decks-container").empty();
                decks.forEach(function (deck) {
                    var deckItem = $(
                        '<div class="deck-item" data-id="' + deck.id + '">' +
                        "<h3>" + deck.name + "</h3>" +
                        "<span>" + deck.cards.length + " cards</span>" +
                        "</div>"
                    );
                    $("#decks-container").append(deckItem);
                });
            },
            error: function () {
                $("#decks-container").html(
                    "<p>Failed to load decks. Is the server running?</p>"
                );
            }
        });
    }

    // Click a deck to open it
    $(document).on("click", ".deck-item", function () {
        var deckId = $(this).data("id");
        $.ajax({
            url: "/api/decks/" + deckId,
            method: "GET",
            dataType: "json",
            success: function (deck) {
                currentDeck = deck;
                currentCardIndex = 0;
                showStudyMode();
            }
        });
    });

    function showStudyMode() {
        $("#deck-list").addClass("hidden");
        $("#quiz-mode").addClass("hidden");
        $("#study-mode").removeClass("hidden");
        $("#deck-title").text(currentDeck.name);
        showCard();
    }

    function showCard() {
        var card = currentDeck.cards[currentCardIndex];
        $("#card-question").text(card.question);
        $("#card-answer").text(card.answer);
        $("#flashcard").removeClass("flipped");
        $("#card-counter").text(
            "Card " + (currentCardIndex + 1) + " of " + currentDeck.cards.length
        );
    }

    // Flip card
    $("#flip-card").on("click", function () {
        $("#flashcard").toggleClass("flipped");
    });

    $("#flashcard").on("click", function () {
        $(this).toggleClass("flipped");
    });

    // Navigate cards
    $("#next-card").on("click", function () {
        if (currentCardIndex < currentDeck.cards.length - 1) {
            currentCardIndex++;
            showCard();
        }
    });

    $("#prev-card").on("click", function () {
        if (currentCardIndex > 0) {
            currentCardIndex--;
            showCard();
        }
    });

    // Back to deck list
    $("#back-to-decks").on("click", function () {
        $("#study-mode").addClass("hidden");
        $("#deck-list").removeClass("hidden");
    });

    // Start Quiz
    $("#start-quiz").on("click", function () {
        quizCards = currentDeck.cards.slice();
        shuffleArray(quizCards);
        quizIndex = 0;
        quizScore = 0;
        $("#study-mode").addClass("hidden");
        $("#quiz-mode").removeClass("hidden");
        $("#quiz-result").addClass("hidden");
        $("#quiz-card").show();
        $("#quiz-options").show();
        showQuizQuestion();
    });

    function showQuizQuestion() {
        var card = quizCards[quizIndex];
        $("#quiz-question").text(card.question);
        $("#quiz-score").text("Score: " + quizScore + " / " + quizCards.length);

        // Generate options: correct answer + 2 wrong answers from other cards
        var options = [card.answer];
        var otherAnswers = currentDeck.cards
            .filter(function (c) {
                return c.answer !== card.answer;
            })
            .map(function (c) {
                return c.answer;
            });
        shuffleArray(otherAnswers);
        options.push(otherAnswers[0], otherAnswers[1]);
        shuffleArray(options);

        $("#quiz-options").empty();
        options.forEach(function (option) {
            var btn = $('<button class="quiz-option"></button>');
            btn.text(option);
            btn.data("answer", option);
            $("#quiz-options").append(btn);
        });
    }

    // Handle quiz answer
    $(document).on("click", ".quiz-option", function () {
        if ($(this).hasClass("correct") || $(this).hasClass("wrong")) {
            return;
        }

        var selected = $(this).data("answer");
        var correct = quizCards[quizIndex].answer;

        // Highlight the correct answer
        $(".quiz-option").each(function () {
            if ($(this).data("answer") === correct) {
                $(this).addClass("correct");
            }
        });

        if (selected === correct) {
            quizScore++;
            $(this).addClass("correct");
        } else {
            $(this).addClass("wrong");
        }

        $("#quiz-score").text("Score: " + quizScore + " / " + quizCards.length);

        // Disable all options after answering
        $(".quiz-option").css("pointer-events", "none");

        // Move to next question after a short delay
        setTimeout(function () {
            quizIndex++;
            if (quizIndex < quizCards.length) {
                $(".quiz-option").css("pointer-events", "auto");
                showQuizQuestion();
            } else {
                showQuizResult();
            }
        }, 1200);
    });

    function showQuizResult() {
        $("#quiz-card").hide();
        $("#quiz-options").hide();
        $("#quiz-result").removeClass("hidden");

        var percentage = Math.round((quizScore / quizCards.length) * 100);
        if (percentage === 100) {
            $("#result-title").text("Perfect Score!");
            $("#result-message").text(
                "You got " + quizScore + " out of " + quizCards.length +
                " correct. Amazing work!"
            );
        } else if (percentage >= 60) {
            $("#result-title").text("Nice Job!");
            $("#result-message").text(
                "You got " + quizScore + " out of " + quizCards.length +
                " correct. Keep studying!"
            );
        } else {
            $("#result-title").text("Keep Practicing!");
            $("#result-message").text(
                "You got " + quizScore + " out of " + quizCards.length +
                " correct. Review the cards and try again."
            );
        }
    }

    // Restart quiz
    $("#restart-quiz").on("click", function () {
        quizCards = currentDeck.cards.slice();
        shuffleArray(quizCards);
        quizIndex = 0;
        quizScore = 0;
        $("#quiz-result").addClass("hidden");
        $("#quiz-card").show();
        $("#quiz-options").show();
        $(".quiz-option").css("pointer-events", "auto");
        showQuizQuestion();
    });

    // Back to study from quiz
    $("#back-to-study").on("click", function () {
        $("#quiz-mode").addClass("hidden");
        showStudyMode();
    });

    // Utility: Fisher-Yates shuffle
    function shuffleArray(array) {
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }
});

Now it's time to test the complete app. Your Java server needs to be restarted so it serves the new app.js file.

  • In IntelliJ, click the red square Stop button in the top toolbar (or the Run panel at the bottom) to stop the server.
  • Right-click FlashcardServer.java and select Run 'FlashcardServer.main()' to start it again.
  • Open Chrome and go to http://localhost:8080.

You should see three deck cards (Java Basics, HTML Fundamentals, CSS Essentials). Click a deck to enter study mode, flip cards, navigate between them, then click Start Quiz to test the quiz. After answering all questions, you should see a results screen with your score.

Quiz not working correctly?

  • If clicking Start Quiz does nothing, open the browser console (F12) and look for a shuffleArray is not defined error. This means the shuffleArray function is missing or placed outside the $(document).ready() wrapper.
  • If you see a blank quiz page, check that showQuizQuestion is spelled correctly everywhere it appears.
  • Make sure you stopped and restarted the server after saving app.js. The Java server caches files, so a restart is required.

Still stuck? Ask for help.

Your full-stack flashcard app is working. The Java backend serves data, and jQuery fetches and displays it in an interactive UI. Next up, take on the Secret Mission to add the ability to create new flashcards from the browser.

Secret mission

Add a "Create Flashcard" Form

Right now, your flashcard data lives entirely in the Java server. What if users could add their own cards directly from the browser? In this secret mission, you'll build a form that sends new flashcard data to the server using a POST request, completing the full request cycle.

Before you go

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing costs. Decide whether to keep your project files, stop the running server, or delete everything entirely.

Resources you used:

  • IntelliJ IDEA project FlashcardQuiz with Java source files.
  • Gson library (2.13.2) added to the project.
  • Web frontend files (index.html, style.css, app.js) in the web/ folder.
  • Java HTTP server process running on port 8080.

Keep everything running

No action needed. Choose this if you want to keep studying or experimenting with your flashcard app.

  • Leave the server running in IntelliJ IDEA.
  • Keep your browser open at http://localhost:8080 to continue using the app.
  • Your project files stay in the FlashcardQuiz folder and you can reopen the project in IntelliJ at any time.

Pause - I'll come back to this later

Stop the server to free up port 8080 and system memory. Your project files stay on disk so you can pick up where you left off.

  • In IntelliJ IDEA, click the red stop button in the top toolbar to stop the server process.
  • Close IntelliJ IDEA.
  • To resume later, reopen IntelliJ IDEA, open the FlashcardQuiz project, and run FlashcardServer.java again.

Delete - I don't want to use this again

Remove all project files and start fresh. This permanently deletes your code.

  • In IntelliJ IDEA, click the red stop button to stop the server if it is still running.
  • Close IntelliJ IDEA.
  • Open Finder (macOS) or File Explorer (Windows) and navigate to the location where IntelliJ created your project.
  • Delete the entire FlashcardQuiz folder.
  • If you no longer need the Gson library for other projects, no extra cleanup is needed. The library was stored inside the project folder.

Mission accomplished

That's a Wrap!

Nice work! You just built a full-stack web application from scratch using a Java backend and a jQuery frontend.

You've learned how to:

  • Build a Java HTTP server using Java's built-in HttpServer class to listen for requests and send responses with no framework needed.
  • Design REST API endpoints that return JSON data, which is how real web applications communicate between frontend and backend.
  • Use jQuery AJAX to fetch data from your Java server and dynamically build an interactive card-flipping study tool and scored quiz.
  • Secret Mission: Add a POST endpoint so users can create new flashcards directly from the browser, completing the full request cycle.

Ready to quiz yourself?