Your first Java program can now start with three lines. Your first useful program still needs clear names, explicit decisions and rules you can test. This guide connects those experiences and explains the packages, classes, records, enums and interfaces behind a growing Java program: start with a tiny runnable file, then build a quotation calculator that knows when shipping should be free.
Researched and verified on 6 November 2026. For readers new to Java; no Spring, database or previous Java project required.
1. Why learn modern Java now?
For this beginner guide, the most useful current Java trend is a simpler path from small programs to real applications. Compact source files and instance main methods became permanent in Java 25. You can begin with the instructions you want to execute and introduce explicit classes as your program grows. This is an established language feature, rather than a Java 27 preview experiment. Oracle’s language-change summary documents its permanent status.
The release context matters, too. Java 27 is the current feature release as of this article’s research date; Java 25 is the latest LTS release listed in Oracle’s support roadmap. LTS describes a vendor support commitment, so check your chosen distributor’s maintenance policy. For the lesson, Java 25 is a practical language baseline; a current JDK 25 or newer can run it. Oracle Java SE Support Roadmap.
| Direction | What it means for a beginner | What to do now |
|---|---|---|
| Smaller entry programs | Learn methods and statements before a full application scaffold | Use a compact source file for the first exercise |
| Records and explicit data | Small value types make inputs and results easier to inspect | Use records for an order line and quotation |
| Runtime improvements in Java 27 | Compact object headers are enabled by default in HotSpot | Learn the JVM model; measure performance later |
| Virtual threads | Useful for applications with many waiting I/O tasks | Learn ordinary methods and tests before concurrency |
The runtime and concurrency trends are real, but they solve later problems. Java 27’s release notes describe the object-header change; the virtual-thread documentation explains that virtual threads support scale for waiting tasks rather than making individual calculations faster. No performance benchmark is claimed for this tutorial. JDK 27 release notes, Oracle virtual-thread guide.
This topic fits Homann Software’s Learning Hub: practical software engineering, followed by architecture and frameworks when the problem calls for them. Java is one useful tool within that broader engineering practice. The choice of a simpler learning path as this article’s top topic is an editorial judgment, not a measured popularity ranking. Homann Software.
2. Install a JDK and understand the tools
Install a maintained Java Development Kit, or JDK, version 25 or newer, for your operating system and CPU. Use an official distributor’s installation instructions, for example Microsoft Build of OpenJDK. A text editor and terminal are enough for this project; an IDE is optional.
Open a new terminal after installation and check both commands:
java --version
javac --version
Both should identify the intended JDK. If one says 21 and the other says 25, fix your PATH or editor JDK selection before continuing. The compact examples need Java 25 language support. The JDK contains development tools such as javac; java starts the runtime that executes your program.
The explicit route is source code, compilation, then execution. With source launch, the java command performs compilation internally. Either route runs compiled code in a Java Virtual Machine. You install a platform-specific JDK, while compatible Java bytecode can be used across supported JVM platforms. Native libraries and operating-system assumptions can still reduce portability. The java launcher specification.
3. Run your first program
Save this exact text in a file named Hello.java:
void main() {
IO.println("Hello, Java!");
}
Run it from the directory containing the file:
java Hello.java
The output is Hello, Java!. Here is what each part means. main is the entry method the launcher invokes. void says this method returns no result value. The braces contain its statements. IO.println prints a line; the text in double quotes is a string. The semicolon ends the statement.
In a compact source file, Java supplies an implicit enclosing class and automatically imports the public types in packages exported by java.base, including IO. You are still writing a method belonging to a class. You can introduce an explicit class when it helps explain the program’s structure. No --enable-preview option is needed for this example on JDK 25+. Oracle’s compact-source tutorial.
Do not name the file Hello.java.txt. On systems that hide known extensions, enable their display in the file manager. Capitalization also matters when you later launch a compiled class.
4. Understand variables, decisions and loops
Now save the following as Basics.java and run java Basics.java:
void main() {
String customer = "Alex";
int quantity = 2;
long unitPriceCents = 1250;
long subtotalCents = unitPriceCents * quantity;
boolean freeShipping = subtotalCents >= 5000;
IO.println(customer + ": " + subtotalCents + " cents");
if (freeShipping) {
IO.println("Free shipping");
} else {
IO.println("Shipping costs 490 cents");
}
for (int item = 1; item <= quantity; item++) {
IO.println("Pack item " + item);
}
}
Read it from top to bottom. String holds text, int holds a whole-number quantity, long holds a larger whole-number amount, and boolean holds true or false. A variable name gives a value a role. The name unitPriceCents also states its unit, which helps prevent accidental mixing of euros and cents.
= assigns a value. * multiplies. >= compares two amounts. The if statement selects a branch; the for loop repeats a statement for item numbers 1 and 2. item++ increases the counter by one after each iteration. + combines text with a value in the print statements. Dev.java’s language basics provides a reference for these constructs.
The program prints a subtotal of 2500 cents, a shipping message and two packing messages. Change quantity to 4 and predict the result before running it: the subtotal becomes 5000 and the free-shipping branch runs. Predicting, executing and explaining the difference is a useful learning habit.
This first exercise assumes small, valid values. The complete version below adds input checks and detects arithmetic overflow. It also moves the calculation into methods so the rules can be reused and tested.
5. Define the business rules before writing more code
Our fictional shop needs a quotation. The user story is: “As a shopper, I want to see the item subtotal, shipping charge and final total before deciding whether to order.” We only calculate that quotation. We do not accept payment or place an order.
| Rule | Decision for this teaching example |
|---|---|
| Currency and unit | EUR, represented as whole cents |
| Basket | Must contain at least one non-null line |
| Product | Must have a non-blank name |
| Price and quantity | Price is zero or positive; quantity is at least one |
| Shipping | 490 cents when subtotal is below 5000 cents; otherwise zero |
| Arithmetic | A result outside the long range is rejected |
| Quotation invariant | Total equals subtotal plus shipping |
Notice the boundary: exactly 5000 cents qualifies for free shipping. Also, a non-empty basket containing only a free item still attracts shipping. These are explicit teaching choices. Tax calculation, discounts, stock, currency conversion and persistence are outside the example.
Whole cents keep this lesson focused on integers. They are a modeling choice for this fixed-currency problem. Systems that calculate fractions, taxes or exchange rates need explicit precision and rounding rules; BigDecimal is one standard Java option. BigDecimal API.
6. Model values with records
A class defines a type of object. An object is a particular instance of that type. A record is a concise kind of class for values described by named components. Java provides its constructor, component accessors such as quantity(), and value-based equals, hashCode and toString behavior. Records became permanent in Java 16. Oracle’s record-class guide.
Save Line.java:
/** One validated order line. All monetary values are EUR cents. */
public record Line(String product, long unitPriceCents, int quantity) {
public Line {
if (product == null || product.isBlank()) {
throw new IllegalArgumentException("Product must have a name");
}
if (unitPriceCents < 0 || quantity < 1) {
throw new IllegalArgumentException("Price must be >= 0; quantity >= 1");
}
}
/** Rejects multiplication overflow rather than wrapping around. */
public long subtotalCents() {
return Math.multiplyExact(unitPriceCents, (long) quantity);
}
}
new Line("Notebook", 1250, 2) creates a line object. Its compact constructor validates the components before construction completes. The || operator means “or” and short-circuits: when the product is null, Java does not call isBlank() on it. throw stops normal execution with an exception that explains the invalid input.
subtotalCents() is an instance method: you call it on a particular line. return passes its calculated value back to the caller. Math.multiplyExact rejects overflow with ArithmeticException; ordinary integer multiplication would otherwise wrap around. Math API.
Save Quote.java:
/** A calculated quotation; construction checks its monetary invariant. */
public record Quote(long subtotalCents, long shippingCents, long totalCents) {
public Quote {
if (subtotalCents < 0 || shippingCents < 0
|| Math.addExact(subtotalCents, shippingCents) != totalCents) {
throw new IllegalArgumentException("Inconsistent quote");
}
}
}
This record protects the relationship between its three amounts. A caller cannot successfully construct a quotation with a negative subtotal, negative shipping, or an inconsistent total. Checking the invariant inside the type protects it wherever the constructor is used.
Record component fields are final, but record immutability is shallow: a record containing a mutable list would still need a defensive-copy decision. Our records contain only numbers and, for Line, an immutable string. This makes their values straightforward to share within this small program. Record-class semantics.
7. Calculate a quotation with a list and a method
Save Checkout.java:
import java.util.List;
/** Pure calculation: no payment, inventory change or database access. */
public final class Checkout {
private static final long FREE_SHIPPING_CENTS = 5000;
private static final long SHIPPING_CENTS = 490;
private Checkout() { }
/** Quotes a non-empty basket using the fictional tutorial shipping rule. */
public static Quote quote(List<Line> lines) {
if (lines == null || lines.isEmpty()) {
throw new IllegalArgumentException("Basket must not be empty");
}
List<Line> basket = List.copyOf(lines);
long subtotalCents = 0;
for (Line line : basket) {
subtotalCents = Math.addExact(subtotalCents, line.subtotalCents());
}
long shippingCents = subtotalCents >= FREE_SHIPPING_CENTS
? 0 : SHIPPING_CENTS;
return new Quote(subtotalCents, shippingCents,
Math.addExact(subtotalCents, shippingCents));
}
}
List<Line> means an ordered collection whose elements are lines. The generic type parameter lets the compiler check how we use its elements. import java.util.List makes that type available by its short name in this explicit source file.
quote is a static method, so callers use Checkout.quote(...) without constructing a checkout object. The private constructor prevents creating unnecessary instances of this utility class. private static final names class-level constants; FREE_SHIPPING_CENTS expresses the policy threshold once.
The enhanced for loop visits each line. The local accumulator starts at zero and grows by each line subtotal. The conditional expression condition ? firstValue : secondValue chooses the shipping charge. The final new Quote(...) constructs the result.
List.copyOf creates an unmodifiable snapshot of the list structure and rejects null elements. It does not clone the elements. In this example the elements are already immutable value objects. The caller must not mutate the original list concurrently while it is being copied. List.copyOf API.
This method has no file access, network request or database update. The same valid inputs produce the same result. That keeps the business calculation easy to inspect and test, and gives a later web controller a small operation to call.
8. Build and run the complete example
Save Shop.java beside the other files:
import java.util.List;
/** Conventional entry point for a growing, multi-file Java application. */
public class Shop {
public static void main(String[] args) {
List<Line> basket = List.of(
new Line("Notebook", 1250, 2),
new Line("Pen", 300, 1));
Quote quote = Checkout.quote(basket);
System.out.println("Subtotal: " + quote.subtotalCents() + " cents");
System.out.println("Shipping: " + quote.shippingCents() + " cents");
System.out.println("Total: " + quote.totalCents() + " cents");
}
}
This is the conventional explicit class and public static void main(String[] args) form. public makes the method accessible, static makes it a class method, and args holds command-line arguments. It remains useful to recognize this entry point in existing projects. The compact first program and this form belong to the same language.
Download and extract the complete beginner lab. Open a terminal inside its extracted directory, then run:
mkdir out
javac --release 25 -d out Line.java Quote.java Checkout.java Shop.java CheckoutTest.java
java -cp out Shop
java -cp out CheckoutTest
If out already exists, continue with the compilation command. -d out selects the destination for compiled class files. -cp out tells the launcher where to find those classes. --release 25 selects Java 25’s language and standard API target when building with a newer JDK. javac tool specification.
The shop prints:
Subtotal: 2800 cents
Shipping: 490 cents
Total: 3290 cents
There is no interactive prompt: edit the sample basket in Shop.java and recompile to try different inputs. Keeping input parsing out of the first complete example makes it easier to see the actual business calculation.
9. Java’s building blocks: how the structures fit together
You have now seen a small working program. Before adding a framework, it helps to distinguish the structures that give a larger Java program its shape. Several of these names describe different aspects of the same code: a record is a kind of class, a class belongs to a package, and an object is an instance of a class.
| Structure | Question it answers | Example in this guide |
|---|---|---|
| Package | Where does this type belong, and what is its full name? | com.homannsoftware.shop.model |
| Ordinary class | Which state and behavior belong together? | Checkout uses a shipping policy |
| Object | Which particular instance are we using? | new Checkout(new StandardShippingPolicy()) |
| Field | What does this object or class retain? | The checkout’s shippingPolicy reference |
| Constructor | What is required to create a valid object? | A non-null ShippingPolicy |
| Method | What operation can a caller request? | quote(basket, delivery) |
| Record | Which named values describe this result or input? | Line and Quote |
| Enum | Which fixed choices does this type allow? | STANDARD, EXPRESS, COLLECTION |
| Interface | What operation must an implementation provide? | ShippingPolicy.feeCents(...) |
| Annotation | What metadata describes a declaration? | @Override asks the compiler to check overriding |
The next stage of the download applies these structures to the same shop. Keep the root-level first version for the initial lessons. The structured/ directory contains the second, independent version. It adds two fictional delivery options: express always costs 990 cents, while collection is free. Standard delivery keeps the original threshold rule.
10. Organize source files with packages and imports
A package gives related types a namespace. In the second project, com.homannsoftware.shop.model.Line is the fully qualified name of the Line record. Reversed domain names are a common naming convention; the compiler does not contact the domain or require a website to run the code. Oracle’s package introduction.
The project uses this layout, relative to structured/:
src/com/homannsoftware/shop/
app/
Shop.java
model/
DeliveryMethod.java
Line.java
Quote.java
service/
Checkout.java
ShippingPolicy.java
StandardShippingPolicy.java
test/com/homannsoftware/shop/
StructuredTest.java
sources.txt
app starts and connects the program. model contains the values and choices the program works with. service contains the calculation and shipping behavior. These names are a teaching organization choice; packages can also be arranged around business capabilities as an application grows.
Each source file declares its package before its imports and type declaration. For example, the packaged Line.java starts with package com.homannsoftware.shop.model;, followed by the same validated record shown earlier. The directory tree mirrors that name under the source root. An import such as import com.homannsoftware.shop.model.Line; allows another file to use Line as a short name. It neither copies the record nor installs a dependency.
The names shop, shop.model and shop.service denote distinct packages. A subpackage receives no special access to its parent’s package-private members. import ...model.* also does not import types from deeper subpackages. Java Language Specification, packages and imports.
An ordinary source file automatically imports java.lang, which contains types such as String and System. The compact source files at the start of the article additionally receive the broader java.base module import. Our packaged files use explicit imports for List and application types. Compact source files cannot declare a named package, so the packaged version uses explicit type declarations.
Packages, source folders and Java modules are separate concepts. src is the chosen source root; it is not part of the package name. This project uses the classpath and has no module-info.java. A Java module can group packages and define exports and dependencies; that is a later lesson rather than a prerequisite here.
11. Represent fixed choices with an enum and switch
The original calculator had one delivery policy. With three supported choices, a string such as "expres" would make spelling errors part of the calculation’s input model. An enum gives each allowed choice a named constant. Save this file as src/com/homannsoftware/shop/model/DeliveryMethod.java:
package com.homannsoftware.shop.model;
/** The three supported delivery choices in this fictional shop. */
public enum DeliveryMethod {
STANDARD,
EXPRESS,
COLLECTION
}
Use DeliveryMethod.EXPRESS to select express delivery. You do not construct it with new. DeliveryMethod.values() provides the constants for iteration; the structured demo uses this to quote all three choices. DeliveryMethod.valueOf("EXPRESS") looks up an exact constant name. An unknown or differently capitalized name throws IllegalArgumentException. An enum reference can still be null, so it does not remove the need for null handling at boundaries. Oracle’s enum guide.
An enum can also contain fields and methods. For this example, keeping the choices in the enum and the pricing in a separate policy makes each responsibility easier to explain. The policy’s switch expression, shown next, maps each choice to a fee. Its arrow cases do not fall through. Covering every enum constant without a default also makes a newly added choice visible as a missing case when the policy is recompiled. Java switch expressions.
12. Define a contract with an interface
An interface names a capability independently of a particular implementation. Here, the calculation needs an object that can provide a shipping fee. This is the complete contract:
package com.homannsoftware.shop.service;
import com.homannsoftware.shop.model.DeliveryMethod;
/** Implementations accept nonnegative subtotals and non-null delivery methods.
* They return a nonnegative fee in EUR cents. */
public interface ShippingPolicy {
long feeCents(long subtotalCents, DeliveryMethod delivery);
}
The method declaration states its inputs and return type. Its documentation also states the behavioral expectations: callers provide a nonnegative subtotal and a delivery choice; implementations return a nonnegative fee. Java’s type checker checks the signature, while our code and tests enforce the value rules.
An ordinary interface method with no body is implicitly public and abstract. Interfaces can also have default, static and private methods, but none are needed for this contract. A class uses implements to provide the operation. Java Language Specification, interfaces.
Here is the implementation used by the demo:
package com.homannsoftware.shop.service;
import com.homannsoftware.shop.model.DeliveryMethod;
import java.util.Objects;
/** Standard: 490 cents below 5000; express: 990; collection: zero. */
public final class StandardShippingPolicy implements ShippingPolicy {
@Override
public long feeCents(long subtotalCents, DeliveryMethod delivery) {
if (subtotalCents < 0) {
throw new IllegalArgumentException("Subtotal must be nonnegative");
}
Objects.requireNonNull(delivery, "Delivery method is required");
return switch (delivery) {
case STANDARD -> subtotalCents >= 5000 ? 0 : 490;
case EXPRESS -> 990;
case COLLECTION -> 0;
};
}
}
@Override asks the compiler to check that this method actually implements or overrides a method from a supertype. The implementation method is public because callers must be able to use the interface’s public operation. The switch returns the selected fee.
An interface is useful when there is a meaningful behavior boundary. Our tests supply a different shipping implementation and verify that checkout uses it. You do not need an interface for every class in an application. In this lesson, it gives one concrete place to see substitution without introducing a framework or dependency-injection container.
13. Understand a class, its objects, constructor and methods
The first Checkout was a static utility. The structured version is an ordinary class whose objects retain their chosen policy. Save it in src/com/homannsoftware/shop/service/Checkout.java:
package com.homannsoftware.shop.service;
import com.homannsoftware.shop.model.DeliveryMethod;
import com.homannsoftware.shop.model.Line;
import com.homannsoftware.shop.model.Quote;
import java.util.List;
import java.util.Objects;
/** A checkout object uses the shipping policy supplied at construction. */
public final class Checkout {
private final ShippingPolicy shippingPolicy;
public Checkout(ShippingPolicy shippingPolicy) {
this.shippingPolicy = Objects.requireNonNull(shippingPolicy,
"Shipping policy is required");
}
public Quote quote(List<Line> lines, DeliveryMethod delivery) {
if (lines == null || lines.isEmpty()) {
throw new IllegalArgumentException("Basket must not be empty");
}
Objects.requireNonNull(delivery, "Delivery method is required");
List<Line> basket = List.copyOf(lines);
long subtotalCents = 0;
for (Line line : basket) {
subtotalCents = Math.addExact(subtotalCents, line.subtotalCents());
}
long shippingCents = shippingPolicy.feeCents(subtotalCents, delivery);
return new Quote(subtotalCents, shippingCents,
Math.addExact(subtotalCents, shippingCents));
}
}
Read this class in three parts. The field stores the policy reference. The constructor requires that reference when the object is created. The quote instance method uses it during a calculation. this.shippingPolicy means the field belonging to the current checkout object; the unqualified constructor parameter has the same name.
The expression new Checkout(new StandardShippingPolicy()) first creates a policy object and then passes it into the checkout constructor. A constructor has the class’s name and no return type, including no void. Because this class explicitly declares a constructor, Java does not also provide an implicit no-argument constructor. Oracle’s constructor guide.
Each checkout object has its own field slot. Two checkout objects can therefore use different policy objects. The field is final, which prevents assigning a different policy reference after construction. This does not, by itself, make the referenced policy object immutable or thread-safe. The class is also final, which prevents subclassing it. Java class and field declarations.
Inside quote, lines and delivery are parameters for one invocation. basket, subtotalCents and shippingCents are local variables. They do not become fields just because the method belongs to a class. This distinction helps you decide whether information should persist with an object or exist only during an operation.
The application creates and uses the object as follows. This is the complete packaged entry point:
package com.homannsoftware.shop.app;
import com.homannsoftware.shop.model.DeliveryMethod;
import com.homannsoftware.shop.model.Line;
import com.homannsoftware.shop.model.Quote;
import com.homannsoftware.shop.service.Checkout;
import com.homannsoftware.shop.service.StandardShippingPolicy;
import java.util.List;
public class Shop {
public static void main(String[] args) {
Checkout checkout = new Checkout(new StandardShippingPolicy());
List<Line> basket = List.of(
new Line("Notebook", 1250, 2), new Line("Pen", 300, 1));
for (DeliveryMethod delivery : DeliveryMethod.values()) {
Quote quote = checkout.quote(basket, delivery);
System.out.println(delivery + ": " + quote.totalCents() + " cents");
}
}
}
main remains static, so the launcher can invoke it without an existing Shop object. It creates the checkout object explicitly. The call checkout.quote(...) then invokes an instance method on that object. Constructor injection is simply the act of supplying the dependency as a constructor argument; no Spring mechanism is involved.
The checkout field has the interface type ShippingPolicy, while the object assigned to it has the concrete class StandardShippingPolicy. When checkout invokes feeCents, Java uses the implementation on that object. This is polymorphism in a small, observable example. The second-stage tests substitute a flat-fee implementation to verify that relationship.
14. Choose visibility and distinguish composition from inheritance
Access modifiers describe which code may use a declaration. For this classpath example, the main choices for members are:
| Modifier | Practical meaning here | Example or reason |
|---|---|---|
public |
Callable from other packages when the enclosing type is accessible | The checkout constructor and quote method |
private |
Internal to the declaring top-level class and its nested types | The shippingPolicy field |
| No modifier | Package-private: accessible within the same package | A helper intentionally hidden from other packages |
protected |
Same-package access plus restricted subclass access across packages | Relevant when deliberately designing an inheritance API |
The cross-package subclass rules for protected are more specific than “visible to every subclass through every reference.” Start with private state and a small public API, then widen access when a caller has a concrete need. default is not an access-modifier keyword; omitting a modifier is what gives a member package-private access. Oracle’s access-control reference.
An ordinary top-level class is public or package-private; it cannot be declared private or protected. With this file-based javac workflow, a public top-level type is placed in a file matching its type name, such as Checkout.java. Nested types have additional possibilities. The structured test runner uses a private nested FlatShipping helper because it belongs only to that test.
Class inheritance uses extends: a subclass builds on a superclass. A class has at most one direct superclass, while it can implement multiple interfaces. An abstract class cannot be instantiated directly and can combine shared state with operations subclasses must supply. These are useful tools when there is a clear subtype relationship. Oracle’s inheritance introduction.
Our checkout uses composition: it has a shipping policy. It does not extend the policy class. This lets the same checkout calculation work with different policy implementations. Records and enums are also specialized class forms: records are implicitly final, and neither records nor enums can choose an arbitrary superclass with extends. Both can implement interfaces. Java Language Specification, record and enum classes.
Use a record when its named component values describe the object’s essential data, an enum for a fixed choice, an interface for a behavioral contract, and an ordinary class when you need to design state and behavior more freely. These choices become easier when you can state the responsibility in a sentence.
15. Compile and test the packaged project
The diagram shows the second-stage packages and the policy relationship. The original UML and BPMN below still describe the first-stage standard-shipping calculation. Express and collection are additions in the structured project; they are not represented by the original BPMN’s single threshold decision.
From the extracted download, enter the structured directory. The supplied sources.txt lists all eight source and test files relative to that directory. Run:
mkdir out
javac --release 25 -d out "@sources.txt"
java -cp out com.homannsoftware.shop.app.Shop
java -cp out com.homannsoftware.shop.StructuredTest
The quotes around @sources.txt also make the command suitable for PowerShell. javac reads the file as an argument list. Launch with the fully qualified class name, using dots and no .java or .class extension. The classpath root is out, rather than the nested directory that contains Shop.class.
The demo prints:
STANDARD: 3290 cents
EXPRESS: 3790 cents
COLLECTION: 2800 cents
The structured runner reports 23 structured checks passed. It covers the delivery options, standard-shipping boundaries, enum lookup, dependency substitution, invalid inputs, invalid policy results and overflow. Together with the original 27 checks, the download now has 50 executable checks. Both project stages were compiled and run independently after extraction from the revised ZIP on the same OpenJDK 27 runtime with a Java 25 compile target.
If the compiler says a package does not exist, check that you are in structured, that every listed source file was extracted, and that you compiled using the supplied argument file. If launch cannot find Shop, use com.homannsoftware.shop.app.Shop and check the classpath root. Do not mix the first-stage out directory with this project’s out directory.
For practice, add SAME_DAY to the enum. Recompile and inspect the missing switch case. Decide its fee rule, add tests, then implement it. This exercise connects a type change to its affected behavior and evidence.
16. Use UML and BPMN to ask different questions
The UML class diagram answers “Which types exist, and what depends on what?” Shop starts the demonstration. Checkout calculates, Line validates and prices one line, and Quote carries a consistent result. The dashed arrows show dependencies. The diagram abbreviates generated record members; the source files are the full implementation.
The BPMN diagram answers “Which decision happens next?” The exclusive gateway selects exactly one shipping route; the merging gateway joins the alternatives. The model shows the normal path for valid, representable inputs. Validation failures and arithmetic errors throw Java exceptions and are omitted from this sketch. The return task includes forming the final total.
The download includes the editable UML PlantUML source and BPMN 2.0 XML with diagram coordinates. The XML declares a non-executable process: this program does not integrate a workflow engine. The graphics are original explanatory drawings; the UML PNG was drawn from the same design rather than rendered by PlantUML. OMG BPMN 2.0.2, OMG UML 2.5.1.
17. Verify the rules with executable checks
The download’s CheckoutTest.java is a dependency-free test runner. It uses explicit checks that throw AssertionError on failure, so checks run without Java’s optional -ea assertion switch. The final successful line is 27 checks passed.
| Area | Checks exercised |
|---|---|
| Valid calculations | Quantity multiplication, multiple lines, record equality |
| Invalid input | Null, empty and blank names; invalid prices and quantities; empty/null baskets and null lines |
| Business boundary | 4999, 5000 and 5001 cents; zero-priced item |
| Value independence | Input list left unchanged; result survives later list edits |
| Arithmetic and invariants | Line/basket overflow, maximum valid amount, inconsistent and negative quotations |
The shipping threshold deserves special attention. A test only at 6000 cents would not distinguish > from >=. Checking one cent below, exactly at, and one cent above the boundary makes that mistake visible.
Verification for this article used OpenJDK 27+35-2325 on Windows. The five explicit source files were compiled with javac --release 25; the tests, shop demo and both compact source examples executed successfully. This is a Java 25 compile target tested on a Java 27 runtime. A separate Java 25 runtime was not executed. The reader ZIP was independently extracted, compiled and run as well.
These checks demonstrate the documented teaching rules. They do not establish tax correctness, payment safety, concurrent operation or production checkout readiness. In a larger project, a test framework such as JUnit and a build tool can provide structured reports and automation; the small runner here keeps the first download self-contained.
18. Troubleshooting and next steps
| Symptom | Likely cause and useful action |
|---|---|
java or javac is not recognized |
Install a JDK, check PATH, and open a fresh terminal |
invalid source release: 25 |
Your compiler is older than 25; check javac --version |
IO or compact syntax is rejected |
Check the JDK and your IDE’s language level |
| Could not find or load main class | Compile first, run from the extracted folder, and use java -cp out Shop |
| UnsupportedClassVersionError | The runtime is older than the compiled target; align JDK versions |
| IllegalArgumentException | Read the message and check the basket/name/quantity/price rule |
| ArithmeticException | The calculation exceeds the chosen number range; reject or redesign the representation |
For practice, change the shipping threshold and update the boundary checks together. Then compare the collection option in the structured project with the original standard-shipping rule. Avoid hiding it in a global variable: a visible parameter makes the decision easier to understand.
Next, practise the package and interface structures above, then learn exception handling, debugging and a build tool. After that, expose a small tested operation through a web framework. Homann Software’s Spring Boot beginner guide is a natural follow-up.
AI assistance can help explain a compiler error or propose test inputs. Keep the same learning loop: predict the result, run the code and explain why it behaves that way. A useful milestone is being able to describe every line of the quotation method without relying on generated commentary.
19. Sources and downloads
Download the tested Java beginner lab, UML and BPMN sources. It contains both project stages, fifteen Java files in total, run instructions, the MIT license and editable diagrams. No Maven, external library, database, Docker or API key is required.
Primary references, checked on 6 November 2026: Oracle language changes, Oracle support roadmap, Java 27 release notes, Dev.java language basics, and the Java API/tool and OMG specifications linked alongside the relevant explanations.
Feature illustration created with Imagegen. The three technical diagrams are original editable drawings by Homann Software.