A stamp card with a printed layout#
Picture a paper loyalty card from a coffee shop. The shop’s name is printed big at the top, your name goes on a line, and nine boxes wait for stamps. The ninth stamp earns a free drink. The rules are in small print on the back.
No law says a card must look like that, but every shop does it, so any card reads in a second. Java has the same two things:
- Naming conventions are the printed layout. A convention is a habit everyone follows that the compiler does not require. Readers rely on it to tell a class from a variable from a constant.
- Javadoc is the small print: a special comment that explains how to use a class, written so a tool can turn it into web pages.
This guide builds one class, LoyaltyCard. It starts badly named and uncommented; each section fixes one thing.
Here is the first draft: a class in lowercase, a method in capitals, one-letter fields. Will it compile? If so, what does it print after 20 stamps?
Show the answer
It compiles and runs: every name is ugly but legal. 20 stamps at 9 per free drink is 20 / 9, which is 2.
public class loyaltycard {
public static final int max = 9;
private String N;
private int s;
public loyaltycard(String n) {
N = n;
}
public void ADD() {
s++;
}
public int Free() {
return s / max;
}
public static void main(String[] args) {
loyaltycard C = new loyaltycard("Rosa");
for (int i = 0; i < 20; i++) {
C.ADD();
}
System.out.println(C.N + ": " + C.s + " stamps, " + C.Free() + " free drinks");
}
}
Rosa: 20 stamps, 2 free drinks
The file is loyaltycard.java because a public class lives in a file of exactly its name. It works, but read the names aloud: ADD shouts, Free looks like a class, C.N says nothing.
s / max divides an int by an int, so Java drops the remainder: 20 / 9 is 2, not 2.22. Here that is what we want, since nobody gets 0.22 of a drink. Shrink it to the boundary: 8 stamps give 0, and exactly 9 give 1, just like the paper card.
Four naming styles, and what each one tells you#
You already pick a word to name a variable, as in int total. Java’s conventions add one thing: the capital letters say what kind of thing the name belongs to.
| Style | Looks like | Used for | On the card |
|---|---|---|---|
| PascalCase | every word capitalized | classes | LoyaltyCard |
| camelCase | first word lowercase, later words capitalized | variables, fields, parameters, methods | ownerName, addStamp |
| UPPER_SNAKE_CASE | all capitals, underscores between words | constants | STAMPS_PER_FREE_DRINK |
| lowercase | all lowercase, parts joined by dots | packages | java.util |
camelCase has humps in the middle, like a camel; PascalCase capitalizes the first word too. A package is a named group of classes. A constant is a static final field: final means it never gets a new value, static that the class shares one copy. Methods are usually verbs (addStamp), variables nouns (stamps), and i is fine for a loop counter.
Rename the draft before you look. What should loyaltycard, max, N, s, ADD and Free become?
Show the answer
LoyaltyCard, STAMPS_PER_FREE_DRINK, ownerName, stamps, addStamp, freeDrinksEarned. Other clear names in the right style are fine: stampCount is as good as stamps.
Only the names change here. A name is just a label, so predict the same output.
LoyaltyCard.javapublic class LoyaltyCard {
public static final int STAMPS_PER_FREE_DRINK = 9;
private String ownerName;
private int stamps;
public LoyaltyCard(String name) {
ownerName = name;
}
public void addStamp() {
stamps++;
}
public int freeDrinksEarned() {
return stamps / STAMPS_PER_FREE_DRINK;
}
public static void main(String[] args) {
LoyaltyCard card = new LoyaltyCard("Rosa");
for (int i = 0; i < 20; i++) {
card.addStamp();
}
System.out.println(card.ownerName + ": " + card.stamps + " stamps, "
+ card.freeDrinksEarned() + " free drinks");
}
}
Rosa: 20 stamps, 2 free drinks
Identical, and now it reads like English: card.addStamp(), card.freeDrinksEarned().
Python writes free_drinks_earned with its docstring inside the function; Java writes freeDrinksEarned with its doc comment above. Python habits still compile, so drop them on purpose.
What the compiler checks, and what it does not#
The first draft proved it: the compiler does not enforce naming conventions. It would happily accept a class called x9_Q. Your teammates would not. What it does check:
- Legal names.
2ndCardis illegal, because a name cannot start with a digit;second_cardis legal, just not conventional. The rules are in identifiers, keywords and literals. - The same spelling every time. Java is case sensitive, so
card.addstamp()fails withcannot find symbol, the error you will meet forScannerbelow. That is also whystringwith a small s fails (see the class-writing guide). - The file name. A
publicclass must sit in a file of exactly its name. The compiler checks that they match, not that the name starts with a capital.
Three kinds of comment#
You met the first two forms in the anatomy of a Java program. The third looks almost like the second, and that is the trap.
| Form | Written as | For |
|---|---|---|
| Line comment | // to the end of the line | a quick note beside one line |
| Block comment | /* … */, any number of lines | a longer note for whoever reads the source |
| Javadoc comment | /** … */: slash and two asterisks | documentation for whoever uses the class |
For everything in this guide, the compiler throws all three away. Only the javadoc tool cares. It is a second program in the JDK, the kit that gave you javac. It reads only comments that start with /** and turns them into web pages. The * on each middle line is neatness; javadoc strips it.
Here is the card with one of each: Javadoc on the class and on addStamp, a line comment on stamps, and a one-asterisk block comment on freeDrinksEarned.
/**
* A coffee-shop loyalty card that collects one stamp per drink.
*
* @author Sam Rivera
* @version 1.0
*/
public class LoyaltyCard {
public static final int STAMPS_PER_FREE_DRINK = 9;
private String ownerName;
private int stamps; // starts at 0, like every int field
public LoyaltyCard(String name) {
ownerName = name;
}
/**
* Adds one stamp for a drink just bought.
*/
public void addStamp() {
stamps++;
}
/*
* Returns how many free drinks the stamps have earned.
*/
public int freeDrinksEarned() {
return stamps / STAMPS_PER_FREE_DRINK;
}
public static void main(String[] args) {
LoyaltyCard card = new LoyaltyCard("Rosa");
for (int i = 0; i < 20; i++) {
card.addStamp();
}
System.out.println(card.ownerName + ": " + card.stamps + " stamps, "
+ card.freeDrinksEarned() + " free drinks");
}
}
Rosa: 20 stamps, 2 free drinks
Same output: the compiler ignored every comment. Now run javadoc on the same file. The $ line is the command typed in a terminal; -d docs means “put the pages in a folder called docs”. This real run is trimmed where you see ....
javadoc warns no comment for each public part it finds undocumented. Which parts of this card will it name?
Show the answer
Four: the constant, the constructor, freeDrinksEarned and main. The comment on freeDrinksEarned opens with /*, so javadoc does not count it.
$ javadoc -d docs LoyaltyCard.java
...
Generating docs/LoyaltyCard.html...
LoyaltyCard.java:8: warning: no comment
public static final int STAMPS_PER_FREE_DRINK = 9;
^
LoyaltyCard.java:12: warning: no comment
public LoyaltyCard(String name) {
^
LoyaltyCard.java:26: warning: no comment
public int freeDrinksEarned() {
^
LoyaltyCard.java:30: warning: no comment
public static void main(String[] args) {
^
...
4 warnings
That last one has a comment, but it opens with /*, so javadoc never saw it.
A comment that opens with /* is an ordinary comment, even if it contains @return, and the compiler will never warn you. Check every doc comment opens with slash, asterisk, asterisk.
Where a Javadoc comment goes, and the class comment#
A Javadoc comment documents whatever is declared directly below it. That position is the only link, so nothing may sit in between.
Two Javadoc comments in this file are misplaced. Which declarations will javadoc say have no comment?
Show the answer
The class and getStamps. The class comment sits above an import line, so it documents nothing. The method comment sits inside the body, below the header, so it is just a comment in the code.
/**
* A coffee-shop loyalty card.
*/
import java.util.ArrayList;
public class LoyaltyCard {
private int stamps;
/** Creates an empty card. */
public LoyaltyCard() {
}
public int getStamps() {
/** Returns the number of stamps. */
return stamps;
}
}$ javadoc -d docs LoyaltyCard.java
...
LoyaltyCard.java:6: warning: no comment
public class LoyaltyCard {
^
LoyaltyCard.java:13: warning: no comment
public int getStamps() {
^
...
2 warnings
So: import lines first, then the class comment, then public class. A method comment goes above the method header, never inside the curly braces. (A line like @Override counts as part of the header, so the Javadoc goes above it.)
Look back at the card’s class comment in the previous section. Its first sentence is the summary, copied into javadoc’s summary tables. Then come block tags, words starting with @ at the start of a line. @author names who wrote the class (one line per author). @version is the version of your class, not of Java, often with a date. javadoc leaves both off the page unless you add -author and -version.
Imports, and why the card does not read the keyboard#
“Imports first” raises a question you may have carried since your first program. Here is a draft of the program that will fill in cards, reading the keyboard with Scanner (see the console input guide).
This draft uses String, System and Scanner, with no import line. Which will the compiler fail to find?
Show the answer
Only Scanner, reported twice because the name appears twice on that line.
public class CardCounter {
public static void main(String[] args) {
Scanner keyboard = new Scanner(System.in);
System.out.print("Name on the card: ");
String name = keyboard.nextLine();
System.out.println("Welcome, " + name);
}
}
CardCounter.java:3: error: cannot find symbol
Scanner keyboard = new Scanner(System.in);
^
symbol: class Scanner
location: class CardCounter
CardCounter.java:3: error: cannot find symbol
Scanner keyboard = new Scanner(System.in);
^
symbol: class Scanner
location: class CardCounter
2 errors
String, System and Math live in the package java.lang, which Java imports into every file automatically. That is why you never imported them. Scanner lives in java.util, so the file needs import java.util.Scanner; at the top, or the fully qualified name java.util.Scanner every time.
Why not give the card its own Scanner field? It would compile once imported, and it is still wrong. Ask: would the card still make sense in an app with no keyboard? The name and stamps belong to the card; the keyboard belongs to the program, and the card should work in a phone app with no console. So input stays beside main, which moves out to its own file, CardCounter.java, and passes the values in.
Method comments: @param and @return#
A method comment has the same shape, with two other tags. @param describes one parameter, exact name first: @param drinks how many free drinks to redeem. @return describes the value handed back. Let the header decide: one @param per parameter, and an @return unless it is void or a constructor.
| Header | @param | @return |
|---|---|---|
public LoyaltyCard(String name) | one, name | none: a constructor |
public void addStamp() | none | none: void |
public int getStamps() | none | one |
public boolean redeem(int drinks) | one, drinks | one |
A void setter (a method that changes a field) gets its @param and no @return.
Write the Javadoc for the getter public int getStamps() (a getter is a method that just hands back a field).
Show the answer
A summary sentence and one @return, with no @param because the parentheses are empty:
/**
* Returns how many stamps are on the card right now.
*
* @return the number of stamps, never negative
*/This draft has three tag mistakes. Use the table above to find them before you read the javadoc run.
Show the answer
@return on the constructor, @return on the void method addStamp, and an @param that starts with a description instead of the name drinks.
/**
* A coffee-shop loyalty card.
*/
public class LoyaltyCard {
private int stamps;
/**
* Creates an empty card.
*
* @return a new card
*/
public LoyaltyCard() {
}
/**
* Adds one stamp.
*
* @return nothing
*/
public void addStamp() {
stamps++;
}
/**
* Checks for enough stamps.
*
* @param how many drinks to check
* @return true if there are enough stamps
*/
public boolean canRedeem(int drinks) {
return stamps >= drinks * 9;
}
}$ javac LoyaltyCard.java
$ javadoc -d docs LoyaltyCard.java
...
LoyaltyCard.java:10: error: invalid use of @return
* @return a new card
^
LoyaltyCard.java:18: error: invalid use of @return
* @return nothing
^
LoyaltyCard.java:27: error: @param name not found
* @param how many drinks to check
^
LoyaltyCard.java:30: warning: no @param for drinks
public boolean canRedeem(int drinks) {
^
...
3 errors
2 warnings
javac printed nothing, so it compiled: tags live inside comments. javadoc reported three errors and two warnings. It read how as the parameter name, found none, and noticed drinks had no @param. The second warning, trimmed here, repeats the constructor mistake in longer words.
Now the finished card, with a getter and a redeem method that trades stamps for drinks. The class-writing guide explains why the fields stay private.
redeem checks stamps >= needed. Shrink it to the boundary: exactly 9 stamps, redeeming 1 drink, needs 9. With >, the customer who earned the drink would be turned away.
So the run below types in a customer with exactly 9 drinks. Predict it first: 1 free drink, the redeem succeeds, 0 stamps left. Bold text is what the user typed.
The file LoyaltyCard.java holds:
/**
* A coffee-shop loyalty card that collects one stamp per drink.
* <p>
* Every <strong>9 stamps</strong> earn one free drink.
*
* @author Sam Rivera
* @version 2.0, October 2026
*/
public class LoyaltyCard {
/** Stamps needed for one free drink. */
public static final int STAMPS_PER_FREE_DRINK = 9;
private String ownerName;
private int stamps;
/**
* Creates an empty card for a new customer.
*
* @param name the name to print on the card
*/
public LoyaltyCard(String name) {
ownerName = name;
}
/**
* Returns how many stamps are on the card right now.
*
* @return the number of stamps, never negative
*/
public int getStamps() {
return stamps;
}
/**
* Adds one stamp for a drink just bought.
*/
public void addStamp() {
stamps++;
}
/**
* Returns how many free drinks the stamps have earned.
*
* @return the number of whole free drinks earned
*/
public int freeDrinksEarned() {
return stamps / STAMPS_PER_FREE_DRINK;
}
/**
* Trades stamps for free drinks, if the card has enough.
*
* @param drinks how many free drinks to redeem
* @return true if the drinks were redeemed, false if not
*/
public boolean redeem(int drinks) {
int needed = drinks * STAMPS_PER_FREE_DRINK;
if (stamps >= needed) {
stamps -= needed;
return true;
}
return false;
}
}
CardCounter.java
import java.util.Scanner;
public class CardCounter {
public static void main(String[] args) {
Scanner keyboard = new Scanner(System.in);
System.out.print("Name on the card: ");
String name = keyboard.nextLine();
System.out.print("Drinks bought: ");
int drinks = keyboard.nextInt();
LoyaltyCard card = new LoyaltyCard(name); // values are passed in
for (int i = 0; i < drinks; i++) {
card.addStamp();
}
System.out.println(name + " has earned " + card.freeDrinksEarned() + " free drink");
System.out.println("Redeem 1: " + card.redeem(1));
System.out.println("Stamps left: " + card.getStamps());
}
}
Name on the card: Theo
Drinks bought: 9
Theo has earned 1 free drink
Redeem 1: true
Stamps left: 0
All three match. Had the redeem printed false, your prediction would have caught the bug. Do the same when an AI tool hands you code: decide what it should print, then compare.
Do not paste it. Add stampsToNextFreeDrink(), which returns how many more stamps the next free drink needs. Write the Javadoc first, choosing the tags from the header alone. Check 13 stamps and 0 stamps by hand.
Show the answer
No parameters, so no @param; it returns an int, so one @return:
/**
* Returns how many more stamps the card needs for its next free drink.
*
* @return a number from 1 to 9
*/
public int stampsToNextFreeDrink() {
return STAMPS_PER_FREE_DRINK - stamps % STAMPS_PER_FREE_DRINK;
}The % operator gives the remainder: 13 stamps leave 4, so 5 more; 0 leave 0, so 9 more.
HTML inside Javadoc, and the web page it becomes#
javadoc writes web pages, which are made of HTML, the tag language browsers read. So everyday HTML tags such as <p> and <strong> are allowed in a Javadoc comment and pass through to the page. javadoc does check them: an old tag such as <font> is reported as an error. The card’s class comment uses <p> to start a paragraph (a blank line alone does not) and <strong> for bold.
The flip side: to HTML, a bare < starts a tag, so writing stamps < 9 in a Javadoc comment makes javadoc report error: malformed HTML. Write < instead, or {@code stamps < 9}, which prints exactly as written. Here is the run on the finished LoyaltyCard.java with -author and -version. This transcript is complete.
$ javadoc -d docs -author -version LoyaltyCard.java
Loading source file LoyaltyCard.java...
Constructing Javadoc information...
Creating destination directory: "docs/"
Building index for all the packages and classes...
Standard Doclet version 21.0.12.1+1-1-24.04.4-Ubuntu
Building tree for all the packages and classes...
Generating docs/LoyaltyCard.html...
Generating docs/package-summary.html...
Generating docs/package-tree.html...
Generating docs/constant-values.html...
Generating docs/overview-tree.html...
Building index for all classes...
Generating docs/allclasses-index.html...
Generating docs/allpackages-index.html...
Generating docs/index-all.html...
Generating docs/search.html...
Generating docs/index.html...
Generating docs/help-doc.html...
No warnings, no errors. The Standard Doclet version line names the JDK that ran javadoc, so yours will show your own version. (A doclet is the part of javadoc that writes the pages.) On JDK 21 the docs folder now holds 31 files: 11 HTML pages (one per Generating line) plus a style sheet, scripts, images and search files. Other JDK versions write a slightly different set.
Open docs/index.html in a browser and click the class name. LoyaltyCard.html shows the summary, “9 stamps” in bold, and Version: and Author: lines. Below come summary tables of the constant, constructor and methods. Each method then gets a detail entry, with Parameters: and Returns: built from your tags.
The private fields are missing: by default javadoc documents only public (and protected) parts, for people who use the class. The official pages for String and Scanner are made by this same tool.
What was new in this guide#
- PascalCase, camelCase, UPPER_SNAKE_CASE and lowercase package names.
- The compiler checks names are legal and consistent, not conventional.
/** */is the only comment javadoc reads.- Javadoc goes directly above what it documents.
@authorand@version.java.langis automatic;Scannerneeds an import and belongs in the program.@paramper parameter,@returnonly when a value comes back.- HTML inside Javadoc.
Naming conventions are habits for people, and Javadoc is a /** */ comment for a tool: the compiler checks neither. Put each Javadoc directly above what it documents, with one @param per parameter and @return only when a value comes back.
Where these go wrong#
- Opening a doc comment with
/*. One asterisk makes an ordinary comment that javadoc ignores. Use/**. @returnon avoidmethod or a constructor. Neither hands back a value; javadoc reportsinvalid use of @return.- Description before the name in
@param. The first word must be the exact parameter name. - Javadoc in the wrong place. Directly above the header: not inside the body, not above the imports.
- Forgetting to import
Scanner.java.utilis not automatic. (ImportingStringis harmless but never needed:java.langis automatic.) - A
Scannerfield in a class likeLoyaltyCard. Read input inmain; pass values in.
On the exam: On a “write the Javadoc” question, marks usually go to the /** opening, a summary sentence, the exact parameter names, and no @return on void. Read the question twice, answer, then reread to check you did every part.
Practice#
Answer each one on paper before you look at the solutions. Every answer that involves running code was checked by compiling and running it.
- Rename every name to follow Java conventions. Which of your changes did the compiler require?
public class library_book { public static final int maxRenewals = 2; private String Title; private int TIMES_RENEWED; public boolean CanRenew() { return TIMES_RENEWED < maxRenewals; } } - Does this compile? If so, what does it print?
public class thermostat { public static final int Max_Temp = 80; public static void main(String[] args) { int T = 68 + 15; if (T > Max_Temp) { T = Max_Temp; } System.out.println("Set to " + T); } } - Find the three mistakes in this comment:
/* * Sets the volume. * @param the new volume, from 0 to 10 * @return the volume */ public void setVolume(int level) { - Write a static method
boxesNeeded(int cookies, int perBox)that returns how many boxes are needed, counting a part-full box as a whole one. Give it a complete Javadoc comment. - A file starts with a Javadoc comment about the class, then
import java.util.Scanner;, thenpublic class Kiosk {, whose constructor and methods all have correct Javadoc. What will javadoc say about the class, and what is the fix? - What error does this give, and what is the usual fix?
public class OrderQueue { public static void main(String[] args) { ArrayList<String> orders = new ArrayList<>(); orders.add("oat latte"); System.out.println(orders.size() + " order waiting"); } }
Solutions#
LibraryBook,MAX_RENEWALS,title,timesRenewed,canRenew. The compiler required none of them. But a renamed public class needs a renamed file:LibraryBook.java.- It compiles: every name is legal and used consistently, just unconventional. 68 + 15 = 83, which is over 80, so
Tbecomes 80.thermostat.javapublic class thermostat { public static final int Max_Temp = 80; public static void main(String[] args) { int T = 68 + 15; if (T > Max_Temp) { T = Max_Temp; } System.out.println("Set to " + T); } }Set to 80 - It opens with
/*, so javadoc ignores it;@parammust start with the namelevel; and avoidmethod gets no@return:/** * Sets the volume. * * @param level the new volume, from 0 to 10 */ public void setVolume(int level) { - Two parameters, two
@paramlines; anintback, one@return. AddingperBox - 1before dividing rounds up; 0 cookies gives11 / 12= 0.Bakery.javapublic class Bakery { /** * Returns how many boxes are needed to pack all the cookies. * A box that is only partly full still counts as one box. * * @param cookies the number of cookies to pack, 0 or more * @param perBox how many cookies fit in one box, at least 1 * @return the number of boxes needed */ public static int boxesNeeded(int cookies, int perBox) { return (cookies + perBox - 1) / perBox; } public static void main(String[] args) { System.out.println(boxesNeeded(25, 12)); System.out.println(boxesNeeded(24, 12)); System.out.println(boxesNeeded(0, 12)); } }3 2 0 warning: no commentfor the class. Animportline cannot carry a Javadoc comment, so the comment above it documents nothing. Put the import first, so the comment sits directly abovepublic class Kiosk.cannot find symbolforclass ArrayList, because it lives injava.util, which is not imported automatically. Addimport java.util.ArrayList;at the top, or writejava.util.ArrayListboth times.OrderQueue.javapublic class OrderQueue { public static void main(String[] args) { ArrayList<String> orders = new ArrayList<>(); orders.add("oat latte"); System.out.println(orders.size() + " order waiting"); } }OrderQueue.java:3: error: cannot find symbol ArrayList<String> orders = new ArrayList<>(); ^ symbol: class ArrayList location: class OrderQueue OrderQueue.java:3: error: cannot find symbol ArrayList<String> orders = new ArrayList<>(); ^ symbol: class ArrayList location: class OrderQueue 2 errors
Test-readiness checklist#
These are the items from the course’s Exam 1 topic list that this guide covers. Tick each one only when you can do it without looking.
- ☐ I can name a class, method, variable and constant in the right style, and know the compiler accepts any style.
- ☐ I can tell
//,/* */and/** */apart and say which one javadoc reads. - ☐ I can place a Javadoc comment and write
@author,@version,@paramand@returncorrectly. - ☐ I can explain why
Stringneeds no import butScannerdoes, and why a class likeLoyaltyCardholds noScanner.
Test yourself in the free Kestrel Exams app
16 practice questions on naming conventions and Javadoc, with every code answer checked by compiling and running it. Free, offline, no account.
Practice naming conventions and Javadoc →Frequently asked questions#
What is the difference between /* */ and /** */ in Java?
To the compiler, none: both are skipped. The javadoc tool reads only comments that start with /** and turns them into HTML documentation. A comment that starts with /* is ordinary, even if it contains tags such as @return.
Does Java enforce naming conventions like camelCase?
No. The compiler accepts a class named loyaltycard and a method named ADD. It only checks that names are legal, that they are spelled the same way every time (Java is case sensitive), and that a public class's name matches its file name.
What naming conventions does Java use?
Classes use PascalCase (LoyaltyCard). Variables, fields, parameters and methods use camelCase (ownerName, addStamp). Constants, which are static final fields, use UPPER_SNAKE_CASE (STAMPS_PER_FREE_DRINK). Package names are all lowercase (java.util).
Does a void method need an @return tag in Javadoc?
No. @return describes the value a method hands back, and a void method hands back nothing. Constructors do not get it either; javadoc reports "invalid use of @return". Parameters still each get an @param line.
How do I turn Javadoc comments into HTML pages?
Run the javadoc tool from the JDK, for example javadoc -d docs LoyaltyCard.java, and open docs/index.html in a browser. Add -author and -version to show those tags.
Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written: several of these exist because somebody asked.