Goal: mission 00's check running and naming the first TODO for you, in about five minutes (the Docker way adds the image build, a few minutes the first time). On your own machine that means installing a JDK, plus Node.js and make for the checks (Maven comes with the project). Codespaces brings Node.js and make, and one apt line adds the JDK; Docker's workshop-java service brings all three.
You'll learn
- What gets installed, and why Node.js is needed even for Java (it runs the checks)
- Where your key lives (
.envin the project folder) and which commands need it - What a failing and a passing check look like, so you recognize them later
Want Python or TypeScript instead? See Python or TypeScript.
Every mission runs in Java on Deepgram's Java SDK (com.deepgram:deepgram-java-sdk 0.11.1), with the same checks as the other tracks. Mission NN's start file is java/missions/mNN_name/Start.java, with the reference answer beside it in Solution.java. Java runs on macOS and Linux, on Windows in WSL, in GitHub Codespaces, in the dev container and in Docker (its own service, workshop-java); you type your turns.
You'll work in two windows the whole time: a terminal and a code editor (VS Code or whatever you already use). Open both now. On macOS the terminal is Terminal; on Linux, any terminal. On Windows, read On Windows first: the project runs there in GitHub Codespaces (a machine GitHub runs for you, in your browser) or WSL (Windows Subsystem for Linux). In Codespaces, both windows sit in one browser tab.
Prefer not to install anything? Two ways round the three tools below: GitHub Codespaces (Can't install anything?, further down) and Docker on your own machine (Run it in Docker). Otherwise:
-
Node.js 22 (22.12 or newer; 20.19 or newer works too). It runs the checks. Check by pasting this into the terminal:
node --version
If that prints
v22.12.0or higher (any newer major,v24orv26say, is fine), or av20fromv20.19.0up, you're set. If it errors or prints something older, install it, then reopen your terminal:- macOS, or any system: the installer from nodejs.org, or nvm, a Node.js version manager (github.com/nvm-sh/nvm):
nvm install 22. - Ubuntu 24.04 or Debian 12: the
nodejspackage there is version 18, too old.curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -thensudo apt-get install -y nodejsgets 22. - Fedora:
sudo dnf install nodejs npm(Fedora 42's is new enough).
- macOS, or any system: the installer from nodejs.org, or nvm, a Node.js version manager (github.com/nvm-sh/nvm):
-
make. Check with
make --version; the first line names it (GNU Make 4.3, say). If it errors: on macOS,xcode-select --installadds it; on Ubuntu or Debian,sudo apt update && sudo apt install make curl git; on Fedora,sudo dnf install make curl git. -
A JDK (Java Development Kit) 17 or newer. Install one: on macOS, with Homebrew (brew.sh),
brew install --cask temurin@21; on Ubuntu 24.04,sudo apt update && sudo apt install openjdk-21-jdk; on Debian 12,sudo apt update && sudo apt install openjdk-17-jdk(17 is enough); on Fedora,sudo dnf install java-21-openjdk-devel; or download Temurin 21 (Adoptium's free build) from adoptium.net. Maven (Java's build tool) comes with the project asjava/mvnw. Reopen your terminal, then check:javac -version
If that prints
javac 17or higher, you're set. The checks needjavac, which comes with a JDK; a JRE (Java Runtime Environment, the kind that only runs programs) hasjavaonly.
Can't install anything? Work laptop, Chromebook, IT policies: run the whole workshop in GitHub Codespaces, free.
That opens an editor and a terminal in the browser, with Node.js and make installed and make setup already run, but no JDK. From there:
- Skip
git clone: you are inside the project. - One
aptline (the package installer on Debian and Ubuntu):sudo apt-get update && sudo apt-get install -y openjdk-21-jdk-headless(under a minute). That line is for the codespace's Debian 13 (trixie) image, which.devcontainer/devcontainer.jsonpins (-headless: no desktop parts). make use java, thenmake setup(it builds the Java helpers and fetches the SDK, as under Do this).- Go to Check that it works: the offline check needs no key.
- Before
make run, add your key (or the two token lines) to.env(see Your API key).
A codespace has no microphone or speakers: type your turns and download the agent's voice to listen (see Hear your agent). VS Code's Dev Containers uses the same setup (.devcontainer/), with the same apt line.
Docker instead? Skip to Run it in Docker; it has its own clone line.
Paste these into your terminal, one at a time:
git clone https://github.com/deepgram-devs/voice-agent-workshop.git
cd voice-agent-workshop
make use java
make setupmake use java goes first, so plain make setup knows to build Java. It installs the check harness, the Node.js program under tools/replay-server/ that replays recorded conversations against your code for make check. Then it builds the Java helpers and fetches the SDK: [setup] Building the Java helpers and fetching the SDK: about a minute, the first time. Maven downloads quietly, so a still screen for up to a minute is normal. It also installs any other track whose tool is on your machine, so a few install lines from them may scroll by; make setup SDK=java installs Java only. It creates .env, and because Java is already picked it ends with your language instead of the pick line:
Created .env: at the workshop, paste the two WORKSHOP_ lines from the /code page into it; at home, put your DEEPGRAM_API_KEY in it
Your language: Java (saved in .workshop-lang). To switch: make use py or make use ts
Run make setup again later and it prints [setup] The Java helpers are built. and skips the build; if the SDK's jars have gone (a cleared ~/.m2, a clone moved to another machine, Docker's down -v), it prints the [setup] Building the Java helpers and fetching the SDK line again and fetches them.
make use java printed Saved: Java. make check, run and live use it now. Add SDK=py to run one command in Python.: from then on make check, make run and make live use Java and name the file they work on; the choice is saved in .workshop-lang (Git ignores it). Pick a language in the README covers switching.
No git on your machine? Download the ZIP instead: green Code button, Download ZIP on the repo page. Unzip it, cd into the folder it makes (voice-agent-workshop-main), and run make use java and make setup there.
The offline check below needs no key. Before make run or make live, open .env in the project folder (make setup created it), find the line DEEPGRAM_API_KEY= and put your key after the =, with no quotes or spaces; or paste the workshop's two token lines. Your API key in the README says where a key comes from and how a Codespaces secret works, the same for every language.
make check 00 # offline: no key, no network, a few secondsThis one fails on purpose: you haven't done mission 00 yet. The command prints Checking your Java file: java/missions/m00_first_words/Start.java, then the checker's [replay] lines, replay: greeting did not pass: and 00-first-words replay "greeting", then this block. It has a PASS line for each part the start file already has, a FAIL line for each open TODO, then the file and line to open:
PASS Your program sends a Settings message after Welcome
PASS Settings is the first message you send
PASS Settings has audio.input (encoding and sample_rate)
PASS Settings has audio.output
PASS Settings has agent.listen.provider.model
PASS Settings has agent.think.provider
FAIL TODO 1: the prompt is your own, not the starter one
PASS TODO 1: the prompt is at least a sentence (20+ characters)
FAIL TODO 2: you picked a voice other than flux-hannah-en
FAIL TODO 3: agent.greeting is set
FAIL TODO 3: the agent says your greeting back to you
Your file: java/missions/m00_first_words/Start.java:21 (TODO 1)
If you see the Your file: line, everything is installed. After the block: 1 failed, 0 passed, and the make: *** [Makefile:...: check] Error 1 under it is the failing check, which is the point. Each mission compiles on its own, in about a second after the first build. Open the file it names and start mission 00; when its checks pass, come back to the README's missions table for the rest (How a mission works there is the reference for the three commands).
Once every TODO passes: 1 passed, then two lines. First Mission 00 passes its offline check: java/missions/m00_first_words/Start.java, then the next step: Next: make run 00 to talk to your agent (type a line, press Enter; Ctrl-D or Ctrl-C hangs up). It needs DEEPGRAM_API_KEY or your workshop token in .env. Then make live 00 runs the live check. Missions with two recorded calls (02 to 05) end 2 passed instead.
In Docker, each make in the Next: line reads docker compose run --rm workshop-java make. With WORKSHOP_URL in .env, two more lines name the mission's page on the workshop site and the checkpoint to tick there, I did this on Your code passed make check NN (At the workshop or remote). Anything else, a setup error say, is under Troubleshooting.
make check 00 MISSION_TARGET=solution runs the reference answer, Solution.java beside your start file, instead of your file: Checking the Java reference answer: java/missions/m00_first_words/Solution.java, the replay, 1 passed, with no Mission 00 passes or Next: line (those are for your start file).
Check yourself. You change PROMPT in your start file, save, and run make check 00 again. Does the check need your key?
Answer
No. The offline check replays a recorded conversation against your code on your machine; nothing reaches Deepgram. make run 00 and make live 00 are the two that do, and they need your key or a workshop token in .env.
Prefer not to install a JDK? Two ways, both with Docker on your own machine. Both need Docker Desktop (macOS, Windows, Linux) or Docker Engine with the Compose plugin (Linux); docker compose version tells you it is there.
- VS Code: install the Dev Containers extension, open this folder, and choose Reopen in Container. On Windows, choose Dev Containers: Clone Repository in Container Volume instead: the project then lives inside Docker, where it runs faster. It builds the same container Codespaces uses (
.devcontainer/) and runsmake setupfor you. Then the oneaptline from Before you start,make use java,make setup, and your key in.env. - Docker only:
compose.yamlandDockerfilein this repo give you a container with Node.js 22 and make, on Debian 12 (bookworm), as root with nosudo. Theworkshop-javaservice is that image plusopenjdk-17-jdk-headlessfrom Debian 12's packages (the Dockerfile'sJDKbuild arg). Paste these into a terminal, one at a time, in this order. The first two lines clone the repo and enter it; the rest run from that folder:
git clone https://github.com/deepgram-devs/voice-agent-workshop.git
cd voice-agent-workshop
docker compose run --rm workshop-java make use java # once: builds the image with a JDK 17 (a few minutes), then picks your language
docker compose run --rm workshop-java make setup # once: the harness, the Java helpers and the other tracks in the image; creates .env
docker compose run --rm workshop-java make check 00 # offline check: no keyBefore the next two commands, open .env in this folder and put your key after DEEPGRAM_API_KEY= (Your API key), or paste the two token lines from the workshop's /code page. A key exported in your shell does not reach the container: put it in .env.
docker compose run --rm workshop-java make run 00 # talk to your agent: type your turns. Needs the key or token
docker compose run --rm workshop-java make live 00 # live check against the real API. Needs the key or token
docker compose run --rm workshop-java # a shell inside the container, for anything else; exit leaves it
docker compose down -v # when you're done: removes the installs (the volumes). To come back, run the make setup line the next make check printsNo git? The ZIP works here too: download it as under Do this, cd voice-agent-workshop-main, and run the docker compose lines from there.
In the container, plain make setup installs every track whose tool is in the image, so other tracks' install lines print first, including a boxed Update available! notice ending To update, run: curl -fsSL https://get.pnpm.io/install.sh | sh -: ignore it, the tool in the image is fine. Then cd java && sh mission.sh setup, [setup] Building the Java helpers and fetching the SDK: about a minute, the first time. (quietly), and it ends with Your language: Java (saved in .workshop-lang). To switch: make use py or make use ts.
make setup SDK=java installs the harness and Java only.
Maven's download lives in the home volume (Docker's named storage, outside the repo), so down -v removes it; the next make setup prints the [setup] Building ... fetching the SDK line again and fetches it. java/target/ is written into the mounted repo.
The plain workshop service has no JDK. make use java there prints Saved: Java. ..., then The Docker image has no JDK. For Java in Docker, use the workshop-java service (docker compose run --rm workshop-java make use java, then make setup); or VS Code Dev Containers, GitHub Codespaces, or your own machine (README: Java)., and exits 0. From then on, with Java saved, make check, make run, make live and make setup there stop with the same command on the other service, for example The Docker image has no JDK, and Java is your language: run this same command with workshop-java in place of workshop (docker compose run --rm workshop-java make check 00); or VS Code Dev Containers, GitHub Codespaces, or your own machine (README: Java). A brief's docker compose run --rm workshop make check 00 line, pasted from the Java track, lands here: run the command the line gives.
Normal, every time:
- The first command builds the image before anything runs. After the volume and network lines (next bullet), Docker's build steps print (
#1,#2, ... lines, thenImage voice-agent-workshop-workshop-java Built): a few minutes and about 1 GB of downloads, so it needs the network. The Java image adds about 430 MB for the JDK; on disk it is about 2.3 GB. Later runs start in a second or two. - Every
docker compose runprints twoContainer voice-agent-workshop-workshop-java-run-... Creating/Createdlines first. The very first run, and the first run afterdown -v, also printVolume ... Creating/Createdlines for the four volumes and the network, each twice. - The prefix is the project name
compose.yamlsets, so it is the same whatever your folder is called. A-pflag on the command changes it, and then every name above reads with yours in place ofvoice-agent-workshop(the image, the containers, thedocker rmibelow). No printed command carries a-p(theNext:lines, theRun docker compose run --rm workshop-java make setup onceline, the "run ... again" lines), so add yours when you paste one. - The repo is mounted into the container, so you edit files on your machine as usual.
.env,.workshop-langandout/last-call.wavare the files it writes in this folder, plusjava/target/and two empty folders where the volumes mount (typescript/node_modules/andtools/replay-server/node_modules/; Git ignores them).down -vleaves them all alone. make checkruns offline with no key.make runandmake livewithout a key or token stop with lines saying so (make liveinside the checker'slive did not pass:framing). Those lines, Docker Compose's own.envcheck andResource is still in useafterdown -vare in the README's Troubleshooting.down -vremoves the volumes, not the image. The nextdocker compose runrecreates them empty, somake checkstops withThe check harness isn't installed. Run docker compose run --rm workshop-java make setup once, then this command again.andmake: *** [Makefile:...: need-sdk] Error 2.- Run that line as printed. It is the plain
make setup: it installs the harness and every track whose tool is in the image, so other tracks' install lines scroll by first, including pnpm's boxedUpdate available!notice (ignore it, as above).make setup SDK=javainstalls the harness and Java only; both work here, and both fetch the SDK again. - To get the space back as well:
docker rmi voice-agent-workshop-workshop-java(andvoice-agent-workshop-workshop, if you built it).
To pipe lines in, add -T: printf 'hello\n' | docker compose run --rm -T workshop-java make run 00 (in PowerShell: "hello" | docker compose run --rm -T workshop-java make run 00). A container has no microphone or speakers: type your turns, then play out/last-call.wav on your machine. With Docker Engine on Linux (the usual rootful install), files and folders the container creates belong to root. From the project folder, sudo chown -R "$(id -u):$(id -g)" . gives them all back to you.
Every make run saves the agent's side of the call to out/last-call.wav when you hang up. So you can hear the voice, speed and greeting you picked. On a laptop, open it in any audio player (on macOS, open out/last-call.wav; on Linux, xdg-open out/last-call.wav; in WSL, explorer.exe out). A call that brought back no audio prints a [no audio] line instead and leaves the file as it was.
In Codespaces, a remote machine or a container, there is no audio device: type your turns as usual, then download the file. In Codespaces or VS Code, right-click out/last-call.wav in the Explorer and choose Download. Over SSH, copy it with scp. Each run writes a new last-call.wav, so download the one you want before the next run.
The microphone runs in one track only, and not this one: type your turns (every mission works typed) and listen to out/last-call.wav; How a mission works in the README has the one way to talk out loud. Wear headphones when you do: without them your agent hears itself through your speakers.
Every one of these has happened to someone smart. The README's Troubleshooting has the rows every language hits; these are Java's own. Find the line you see, read what it means, apply the fix, carry on.
| You see | What's happening | Fix |
|---|---|---|
Java needs a JDK 17 or newer / Skipping Java: it needs a JDK 17 or newer / No javac on the PATH |
No JDK 17 or newer on your PATH (the list of folders your terminal searches for commands). No javac on the PATH. The Java here is a runtime only. means what you have is a JRE: a JDK includes javac. |
Install a JDK 17 or newer (Before you start, above; on macOS the system's java only offers to install one), open a new terminal, and run make setup. In Codespaces and the dev container: sudo apt-get update && sudo apt-get install -y openjdk-21-jdk-headless, then make setup. |
[setup] Building the Java helpers and fetching the SDK |
The first make setup, or any check or run whose jars have gone (Docker's down -v, a cleared ~/.m2), downloads Maven and the SDK: up to a minute, and Maven prints nothing while it does, so a still screen is normal. Each check after that takes about a second. |
Nothing to do; wait for it. |
[setup] The Java helpers are built. |
A later make setup: the helpers and the SDK are in place, nothing to build. |
Nothing to do. |
[setup] Building the Java helpers again (they changed). |
A later make setup or check after java/src/main/java or pom.xml changed (a git pull, say): a rebuild of a few seconds. |
Nothing to do. |
The Docker image has no JDK. For Java in Docker, use the workshop-java service ... (after make use java), or The Docker image has no JDK, and Java is your language: run this same command with workshop-java in place of workshop (docker compose run --rm workshop-java make ...) (after any command once Java is saved; after make setup: Skipping Java: ... first, then make: *** [Makefile:...: setup] Error 2) |
You picked Java in the plain workshop container (make use java, make setup, or any make ... SDK=java). That image has no JDK. make setup still created .env, then stopped on Java's line. |
Run the command the line gives: the same one on the workshop-java service (Run it in Docker). |
.env line N couldn't be read |
A quote on line N never closes or has words after it, or the line isn't KEY=value. On the key's line, the is empty line follows it: fix line N, not the key. It prints right under the first line (Running your Java file:, Checking ... or Live check ...), before anything else. |
Write the line as KEY=value, no quotes. In Docker, Compose usually stops on these lines first: the two failed to read rows in the README's Troubleshooting. One word after a closing quote gets through and reaches this row. |
Could not reach the agent: connection refused. Check DEEPGRAM_AGENT_URL. on make run (... Check WORKSHOP_URL. Is the workshop site up? with a token), then make: *** [Makefile:...: run] Error 2; in make live, FAIL Could not reach production: ... under live did not pass: |
Nothing answered, or the address did not resolve: venue wifi, a proxy, a VPN, or a wrong DEEPGRAM_AGENT_URL. In place of connection refused, the line says no such host, it timed out or an exception's name, or none at all: Could not reach the agent. Check DEEPGRAM_AGENT_URL. |
Leave DEEPGRAM_AGENT_URL empty for production; if it is, try a phone hotspot. make check NN needs no network. The README's Troubleshooting has the three rows (custom endpoint or workshop proxy in place of production for another address or a token). |
Your Java file doesn't compile: fix the first error above (java/missions/mNN_name/Start.java:N), then run make check NN again. |
The javac line above names the file and line, from the project folder. The mistake is on or right above that line: ';' expected is on the line that lost the ;, caret at its last token, except when the next line is @Override (then illegal start of expression there, a line or a few below). |
Fix it and run the command the line names (it ends make check NN SDK=java again. when Java is not your saved language, and in Docker it starts docker compose run --rm workshop-java make). Each mission compiles on its own, so your other missions still run their checks. |
Your editor flags agent.put("name", obj(...)) as deprecated |
put with an object is a deprecated Jackson call. The check compiles with warnings off, so it passes either way. |
Use agent.set("name", obj(...)) for an object (put is for a string or a number). |
MIC=1 runs in Python: make run NN SDK=py MIC=1 (add MISSION_TARGET=solution to use the reference answer) (in Docker, No microphone in a container. Type your turns: make run NN (Docker: docker compose run --rm workshop-java make run NN) prints instead) |
You added MIC=1; the microphone runs in one track only, and not this one. |
Type your turns, or run the command the line gives. |
The missions are in the README: Missions, then Keep going. Three Java things to know on the way:
- The smallest program, with no helpers:
sh mission.sh example(fromjava/; in Docker,docker compose run --rm workshop-java sh -c 'cd java && sh mission.sh example'). It sends Settings, waits forSettingsApplied, lets you type turns and saves the agent's voice toout/last-call.wav. With no key it stops onDEEPGRAM_API_KEY is empty in .env (this folder). Set it: this example runs on your own key only (make run takes a workshop token too). make check NN runs offline with no key.: "this folder" is the project folder, where.envlives, even fromjava/, andNNbecause the example has no check. Nothing answering atDEEPGRAM_AGENT_URLprintsCould not reach the agent: connection refused. Check DEEPGRAM_AGENT_URL., asmake rundoes. - Staying connected.
runends when the connection drops; Java has no resilient runner here, so run the program again after a drop (Staying connected says which drops those are). - The helpers.
java/src/main/java/agent/holds every SDK call. A mission implementsagent.Mission(buildSettings(),onEvent(agent, event)), callsCommon.run(new Start(), args), reads events withevent.type(),event.str("...")andevent.items("..."), and never importscom.deepgram.sh mission.sh check 00-first-wordsfromjava/runs one check without make'sChecking your Java file:header or theNext:line (those are make's).