← Back to Knowledge Base

Java Card Applet Tutorial: Build Your First Smart Card Application

Updated July 27, 2026 · CardWise Knowledge Base · ~15 min read
What you'll learn: Write a complete Java Card applet from scratch, handle APDU commands, compile to a CAP file, and install it on a smart card using GlobalPlatform. All code examples are tested on Java Card 3.0.4 Classic.

1. Java Card Platform Overview

Java Card is a software platform that enables Java-based applications (called applets) to run on resource-constrained smart cards and secure elements. Unlike standard Java, Java Card uses a subset of the language with no threads, no garbage collection (on Classic edition), and a strict sandboxed security model where each applet is isolated in its own security domain.

FeatureStandard Java (J2SE)Java Card
Heap sizeHundreds of MB~2-128 KB
Data typesint, long, double, etc.byte, short (no long, float, double in Classic)
ThreadsFull multi-threadingSingle-threaded
GCGenerational GCOptional (Classic: manual memory mgmt)
Class librariesThousandsjavacard.framework + extensions
Deployment formatJAR (class files)CAP (Converted Applet) file
LifecycleJVM start/stopPersistent: applets survive power cycles

2. Development Environment Setup

Option A: JCIDE (Recommended for Beginners)

JCIDE (Java Card Integrated Development Environment) is a free, all-in-one IDE that includes the Java Card SDK, simulator, CAP converter, and debugger. Download from the JavaCardOS Tools page.

Option B: Eclipse + Java Card SDK

  1. Install Eclipse IDE for Java
  2. Download Java Card SDK (e.g., Oracle Java Card 3.0.5 or NXP JCOP SDK)
  3. Configure the converter tool path in Eclipse build settings
  4. Use GlobalPlatformPro (gp.jar/gp.exe) for CAP installation
Pro tip: Use GlobalPlatformPro (gp --install applet.cap) to load CAP files onto real cards. It's the de facto standard open-source tool for GP card management. Install with: brew install gp (macOS) or download from GitHub.

3. Your First Applet: "Hello Card"

package com.cardwise.hellocard;

import javacard.framework.*;

public class HelloCardApplet extends Applet {

    // APDU instruction codes
    private static final byte INS_SAY_HELLO = (byte) 0x10;
    private static final byte INS_SET_NAME   = (byte) 0x20;

    // Applet data (persistent across power cycles)
    private byte[] storedName;
    private static final short MAX_NAME_LEN = 32;

    /**
     * Called once when applet is installed on the card.
     * Use this to allocate persistent memory and set initial state.
     */
    public static void install(byte[] bArray, short bOffset, byte bLength) {
        // Create applet instance and register with JCRE
        HelloCardApplet applet = new HelloCardApplet();
        applet.storedName = new byte[MAX_NAME_LEN];
        applet.register(bArray, (short)(bOffset + 1), bArray[bOffset]);
    }

    /**
     * APDU dispatcher — called for every incoming APDU command.
     */
    public void process(APDU apdu) {
        byte[] buffer = apdu.getBuffer();

        // Only process SELECT commands at the applet level
        if (selectingApplet()) {
            return;
        }

        // Parse the APDU header
        byte cla = buffer[ISO7816.OFFSET_CLA];
        byte ins = buffer[ISO7816.OFFSET_INS];

        if (cla != 0x00) {
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }

        switch (ins) {
            case INS_SAY_HELLO:
                handleSayHello(apdu);
                break;
            case INS_SET_NAME:
                handleSetName(apdu);
                break;
            default:
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    }

    /**
     * Return a greeting string.
     * APDU: 00 10 00 00 [Le]
     */
    private void handleSayHello(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        short le = apdu.setOutgoing();

        // Build response: "Hello, [name]!"
        byte[] greeting = {'H','e','l','l','o',',',' '};
        short len = (short) greeting.length;
        Util.arrayCopyNonAtomic(greeting, (short) 0, buffer, (short) 0, len);

        // Append stored name
        short nameLen = 0;
        for (short i = 0; i < storedName.length && storedName[i] != 0; i++) {
            nameLen++;
        }
        if (nameLen > 0) {
            Util.arrayCopyNonAtomic(storedName, (short) 0, buffer, len, nameLen);
            len += nameLen;
        }
        buffer[len++] = '!';

        apdu.setOutgoingLength(len);
        apdu.sendBytes((short) 0, len);
    }

    /**
     * Store a name sent in the APDU data field.
     * APDU: 00 20 00 00 [Lc] [name bytes]
     */
    private void handleSetName(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        short bytesRead = apdu.setIncomingAndReceive();
        short dataOffset = apdu.getOffsetCdata();

        // Copy name data into persistent storage
        short copyLen = bytesRead < MAX_NAME_LEN ? bytesRead : MAX_NAME_LEN;
        Util.arrayCopyNonAtomic(buffer, dataOffset, storedName, (short) 0, copyLen);
        // Zero-fill remaining bytes
        for (short i = copyLen; i < MAX_NAME_LEN; i++) {
            storedName[i] = 0;
        }
    }
}

4. APDU Handling Patterns

Case 2: Incoming Data, No Outgoing Data

// APDU: CLA INS P1 P2 Lc Data
// Typical for: VERIFY, UPDATE BINARY, PUT DATA
short bytesRead = apdu.setIncomingAndReceive();
short offset = apdu.getOffsetCdata();
// ... process data at buffer[offset] ...
// No send — just success (SW=9000 implicit)

Case 4: Incoming Data + Outgoing Data

// APDU: CLA INS P1 P2 Lc Data Le
// Typical for: SELECT, INTERNAL AUTHENTICATE
short bytesRead = apdu.setIncomingAndReceive();
// ... process input data ...
short le = apdu.setOutgoing();
// ... fill buffer[0..le-1] with response data ...
apdu.setOutgoingLength(responseLen);
apdu.sendBytes((short) 0, responseLen);

5. Compilation & CAP File Generation

# Step 1: Compile .java to .class (standard javac with JC SDK)
javac -cp javacard.jar -d bin/ src/com/cardwise/hellocard/*.java

# Step 2: Convert .class to .cap (Java Card converter tool)
converter -classdir bin/ \
  -applet 0x01:0x02:0x03:0x04:0x05:0x01 \
  -package 0x01:0x02:0x03:0x04:0x05 \
  -out CAP \
  com.cardwise.hellocard 0x01:0x02:0x03:0x04:0x05 1.0

# Step 3: Install CAP on card via GlobalPlatform
gp --install hellocard.cap

# Step 4: Verify installation
gp --list

6. Key Java Card API Classes

ClassPackagePurpose
Appletjavacard.frameworkBase class for all applets. Implements install() and process().
APDUjavacard.frameworkWraps incoming/outgoing APDU buffer. Core I/O class.
ISO7816javacard.frameworkConstants: SW values, CLA/INS/P1/P2 offsets.
ISOExceptionjavacard.frameworkThrow with SW1 SW2 to signal errors. throwIt(SW_FILE_NOT_FOUND).
Utiljavacard.frameworkArray copy/compare utilities (no System.arraycopy()).
OwnerPINjavacard.frameworkPIN management: check(), update(), getTriesRemaining().
Signaturejavacard.securityOn-card digital signatures (RSA/ECC).
KeyBuilderjavacard.securityCreate DES/AES/RSA/ECC key objects on the card.
Cipherjavacardx.cryptoOn-card encryption/decryption (AES-CBC, DES-CBC, etc.).

7. Real-World Deployment Checklist

  1. Key diversification: Use a KDF to derive per-card applet keys from a master key + card serial number
  2. Secure messaging: Use SCP02 or SCP03 for all personalization APDUs
  3. Transaction atomicity: Use JCSystem.beginTransaction()/commitTransaction() for multi-step state changes
  4. Applet firewall: Use Shareable interface for inter-applet communication
  5. Memory budget: Monitor transient vs persistent memory usage. Use JCSystem.getAvailableMemory()
Related reading: ISO 7816 Complete Guide for the underlying APDU protocol, SCP02 vs SCP03 for secure channel setup during applet installation.