Skip to content

Latest commit

 

History

History
1650 lines (1326 loc) · 55.9 KB

File metadata and controls

1650 lines (1326 loc) · 55.9 KB

AI Assistant Rules for Apache Juneau

This document outlines the rules, guidelines, and best practices that AI assistants follow when working on the Apache Juneau project.

Note: This file is referred to as "my rules" and serves as the definitive reference for all guidelines and conventions I follow when working on the Apache Juneau project.

Important: Also read AI_SESSION.md to understand the current session's work context, what we're currently working on, and any recent changes or patterns established in this session.

Documentation Separation Rule: AI_SESSION.md should only contain session-specific information that is not already covered in AGENTS.md. General rules, permanent conventions, and best practices belong in AGENTS.md. Session-specific work progress, current tasks, and temporary patterns belong in AI_SESSION.md.

User Commands

Shorthand Commands

  • "c" means "continue" - When the user sends just "c", continue with the current task or work
  • "s" means "status" - When the user sends just "s", give a status update on what you're currently working on
  • "TODO-x" means "work on this TODO" - When the user sends just "TODO-3", "TODO-67", etc., start working on that specific TODO item from the /todo/TODO.md file

Task Interpretation Commands

  • "make a plan" or "come up with a plan" - When the user asks to make a plan for something, provide a summary of suggested changes only. Do NOT make actual code changes. The plan should outline what needs to be done, but implementation should wait for explicit user approval.
  • "suppress warnings" or "suppress issues" - When the user asks to suppress warnings or issues that appear to refer to SonarLint/SonarQube issues, add @SuppressWarnings annotations to the appropriate class or method level. Use the specific rule ID from the warning (e.g., java:S100, java:S115, java:S116). Apply class-level suppressions when multiple methods/fields are affected, or method-level suppressions for specific methods.
  • SuppressWarnings Format: All @SuppressWarnings annotations must use multi-line format with curly braces and include a brief comment explaining why each suppression is needed. Never use single-line @SuppressWarnings("xxx") without the multi-line format and comment.
    @SuppressWarnings({
        "resource" // CsvReader owns ParserReader; caller must close CsvReader
    })
    Or for multiple suppressions:
    @SuppressWarnings({
        "rawtypes", // Raw types necessary for generic type handling
        "unchecked", // Type erasure requires unchecked casts
        "resource" // w is closed by try-with-resources; lambdas capture it
    })
    Exception: Parameter-level annotations (e.g. catch (@SuppressWarnings("unused") Exception e)) may remain single-line when multi-line format is impractical.

Script Shortcut Commands

  • "start docs" or "start docusaurus" - Runs scripts/start-docusaurus.py
  • "revert staged" - Runs scripts/revert-staged.py
  • "revert unstaged" - Runs scripts/revert-unstaged.py
  • "start jetty" - Runs scripts/start-examples-rest-jetty.py
  • "start springboot" - Runs scripts/start-examples-springboot.py
  • "push" or "push changes" - When the user asks to push changes, use ./scripts/push.py "Commit message" to push changes. Always prompt the user for the commit message and provide 2-3 suggested commit message options based on the changes made. Example: ./scripts/push.py "Replace Stream.collect(Collectors.toList()) with Stream.toList()"
  • "test" - Runs scripts/test.py

Documentation Commands

  • "save a rule" or "save this rule" - Add the rule/information to AGENTS.md (permanent/general)
  • "store this rule in the session" - Add the rule/information to AI_SESSION.md (session-specific)
  • "store this rule in the context" - Add the rule/information to AGENTS.md (permanent/general)

Core Working Principles

0. Change Evaluation

  • After each code modification, provide a brief (1-2 sentence) evaluation of the change
  • Explain what was changed and why it improves the codebase
  • This helps document the reasoning behind modifications and ensures changes are intentional

1. Code Quality and Consistency

  • Follow existing code patterns and conventions
  • Maintain consistency with the existing codebase
  • Use established naming conventions and formatting
  • Preserve existing functionality while making improvements

Indentation Rules

When adding or modifying Java code, ALWAYS preserve the exact indentation of the surrounding code:

Critical Requirements:

  1. Use tabs, not spaces - Java files in this project use tab characters for indentation
  2. Match surrounding code exactly - Copy the leading whitespace character-for-character from the old_string
  3. Preserve indentation levels - Count tabs in nearby lines and use the same number

Common Patterns:

<TAB>/**
<TAB> * Javadoc line 1
<TAB> * Javadoc line 2
<TAB> */
<TAB>@Annotation
<TAB>public ReturnType methodName() {
<TAB><TAB>return statement;
<TAB>}

Where <TAB> represents an actual tab character (\t), not spaces.

Why This Matters:

  • Incorrect indentation (offset by one tab to the left) is a common error
  • Mixed tabs/spaces cause inconsistent formatting
  • IDE auto-formatting relies on consistent indentation
  • Project standards mandate tab-based indentation

Verification Steps:

  1. Before creating new_string, examine the indentation in old_string
  2. Count the tab characters at the start of each line
  3. Copy the exact indentation pattern (including all tabs)
  4. For new Javadoc, match the indentation of nearby methods

Example Correction:

// WRONG - Missing leading tab
/**
 * Method description
 */
public void method() {

// CORRECT - Includes leading tab
<TAB>/**
<TAB> * Method description
<TAB> */
<TAB>public void method() {

Exception Handling

When throwing exceptions in Java code:

  1. Use ThrowableUtils methods instead of direct exception constructors:

    • Use illegalArg(String message, Object... args) for IllegalArgumentException
    • Use runtimeException(String message, Object... args) for generic RuntimeException
    • Add static import: import static org.apache.juneau.common.utils.ThrowableUtils.*;
  2. Use MessageFormat-style placeholders with ThrowableUtils methods:

    • Use {0}, {1}, etc. for parameter placeholders
    • Escape single quotes with '' (e.g., "Value ''{0}'' is invalid")
    • Pass arguments as varargs after the message string

Examples:

// WRONG - Direct exception constructor
throw new IllegalArgumentException("Value '" + value + "' is invalid");

// CORRECT - ThrowableUtils with MessageFormat
throw illegalArg("Value ''{0}'' is invalid", value);

// WRONG - String concatenation
throw new RuntimeException("Failed to process " + name + " with id " + id);

// CORRECT - ThrowableUtils with multiple arguments
throw runtimeException("Failed to process {0} with id {1}", name, id);

Local Variable Type Inference

When declaring local variables in Java code:

  1. Use the var keyword whenever the type is obvious from the right-hand side:

    • Initializers with constructor calls: var map = new HashMap<String,Integer>();
    • Method calls with clear return types: var list = getList();
    • Stream operations: var result = stream.collect(toList());
    • Enhanced for loops: for (var entry : map.entrySet())
  2. Keep explicit types when:

    • The type is not obvious from the initializer: InputStream stream = getStream();
    • Readability would suffer: boolean hasNext = iterator.hasNext(); (better than var hasNext)
    • Generic type parameters need to be preserved on left side: List<String> list = new ArrayList<>();

Examples:

// WRONG - Redundant type declaration
Map<String,Integer> map = new HashMap<String,Integer>();
List<String> keys = map.keySet();

// CORRECT - Use var
var map = new HashMap<String,Integer>();
var keys = map.keySet();

Instanceof Pattern Matching Variable Naming

When using instanceof pattern matching (Java 16+), follow this naming convention:

  1. General Rule: Append "2" to the original variable name:

    • if (o instanceof Type o2) - original variable is o, pattern variable is o2
    • if (value instanceof Calendar value2) - original variable is value, pattern variable is value2
    • if (this instanceof MethodInfo this2) - original variable is this, pattern variable is this2
  2. Exception for variables ending in "2": When the original variable already ends in "2" (like o2), use "o3" instead of "o22" for simplicity:

    • if (o2 instanceof InputStream o3) - original variable is o2, pattern variable is o3
    • if (o1 instanceof Comparable o3) - original variable is o1, pattern variable is o3 (not o12)
  3. Conflict Resolution: When there's a naming conflict (e.g., a lambda parameter with the same name), use a different name:

    • if (value instanceof BeanMap<?> value2) followed by value2.forEachValue(..., (pMeta2, key2, value3, thrown2) -> ...) - lambda parameter uses value3 to avoid conflict with pattern variable value2

Examples:

// CORRECT - Standard pattern
if (o instanceof BeanMap o2)
    serializeBeanMap(out, o2, typeName);

// CORRECT - Variable ending in "2"
for (var o2 : o) {
    if (o2 instanceof InputStream o3)
        o3.close();
}

// CORRECT - Conflict resolution
if (value instanceof BeanMap<?> value2) {
    value2.forEachValue(x -> true, (pMeta2, key2, value3, thrown2) -> {
        // value3 used to avoid conflict with value2
    });
}

// WRONG - Not following convention
if (o instanceof BeanMap bm)  // Should be o2
if (value instanceof Calendar cal)  // Should be value2

Final Fields and Memoization Pattern

When declaring class fields, always use final to ensure true immutability:

  1. Always use final for fields:

    • All instance fields should be declared final whenever possible
    • This provides compile-time immutability guarantees
    • Prevents accidental modification after construction
  2. Use find helper methods for memoized fields:

    • When memoized Supplier fields need constructor parameters, use helper methods
    • Name helper methods with the find prefix (e.g., findGenericInterfaces())
    • Helper methods are called during field initialization and can access constructor parameters
    • This allows final fields to depend on constructor parameters
  3. Why this matters:

    • Provides stronger immutability guarantees than "effectively final" comments
    • Compiler enforces immutability rather than relying on conventions
    • Makes the code more maintainable and less error-prone
    • Documents thread-safety guarantees explicitly

Examples:

// WRONG - Non-final field with comment
Class<?> c;  // Effectively final

// CORRECT - Final field with helper method pattern
public class ClassInfo {
	private final Class<?> c;
	
	// Final supplier initialized using helper method that accesses constructor parameter
	private final Supplier<List<Type>> genericInterfacesCache = memoize(this::findGenericInterfaces);
	
	public ClassInfo(Class<?> c) {
		this.c = c;
	}
	
	// Helper method called during field initialization
	private List<Type> findGenericInterfaces() {
		return c == null ? Collections.emptyList() : u(l(c.getGenericInterfaces()));
	}
}

Pattern Summary:

  1. Declare constructor parameters as final fields
  2. Create find helper methods that use those fields
  3. Initialize memoized Supplier fields with method references to the helper methods
  4. This allows all fields to be truly final while still supporting lazy initialization

Git Operations and Reverts

When working with git operations, especially reverting changes:

  1. Use helper scripts for reverting - Always use the provided Python scripts instead of git commands directly:

    • ✅ CORRECT: ./scripts/revert-unstaged.py path/to/specific/File.java (reverts unstaged to staged)
    • ✅ CORRECT: ./scripts/revert-staged.py path/to/specific/File.java (reverts to HEAD, discards all changes)
    • ❌ WRONG: Using git restore, git checkout, or any git commands directly
  2. Revert Unstaged Changes Script - ./scripts/revert-unstaged.py

    • Reverts working directory changes back to the staged (INDEX) version
    • Preserves staged changes that have been tested
    • Use this when you have staged changes you want to keep
    • Command: git restore --source=INDEX <file>
  3. Revert Staged Changes Script - ./scripts/revert-staged.py

    • Reverts both staged AND unstaged changes back to HEAD (last commit)
    • ⚠️ WARNING: Discards all changes (staged and unstaged)
    • Use this when you want to completely discard all changes to a file
    • Command: git restore --source=HEAD <file>
  4. Always revert one file at a time - Never use broad wildcards or multiple file paths:

    • ✅ CORRECT: ./scripts/revert-unstaged.py path/to/specific/File.java
    • ❌ WRONG: Reverting multiple files or using wildcards
  5. Why this matters:

    • Preserves staged changes: Staged changes have been tested and should not be lost
    • Surgical precision: Only reverts the specific problematic file's changes
    • Safe recovery: If a file is staged, it means it was working at that point
    • Prevents data loss: Won't accidentally revert good changes along with bad ones
    • User-friendly: Scripts provide clear feedback and prevent common mistakes
  6. Proper workflow when compilation fails:

    • Identify the specific file(s) causing the error
    • Revert only that file's unstaged changes: ./scripts/revert-unstaged.py path/to/ProblematicFile.java
    • Verify the revert fixed the issue (back to staged/tested version)
    • Then address that specific file's changes separately

Examples:

# CORRECT - Revert unstaged changes only (preserves staged)
./scripts/revert-unstaged.py juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSearcher.java

# CORRECT - Revert all changes back to HEAD (discards everything)
./scripts/revert-staged.py juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSearcher.java

# WRONG - Don't use git commands directly
git restore --source=INDEX path/to/file
git checkout -- path/to/file

Understanding the scripts:

  • revert-unstaged.py: Uses git restore --source=INDEX to revert to staged version
  • revert-staged.py: Uses git restore --source=HEAD to revert to last commit
  • Both scripts handle one file at a time for safety
  • Both provide clear feedback about what they're doing

2. Testing Standards

  • Ensure comprehensive test coverage for all changes
  • Follow the established unit testing patterns
  • Use the Sequential Single-Letter Label Convention (SSLLC)
  • Implement proper assertion patterns using assertBean()

3. Documentation Standards

  • Maintain comprehensive javadoc documentation
  • Follow established documentation formatting rules
  • Include practical examples in documentation
  • Link to relevant specifications and resources

4. Build and Compilation Issues

IMPORTANT: Maven vs IDE Compilation Divergence

When compiling code using Maven and the user compiles through their IDE (Eclipse), the compiled code can diverge, leading to unexpected behavior such as:

  • Code changes not being reflected in test runs
  • Old code behavior persisting despite source changes
  • Stale class files causing incorrect results

Eclipse "Build Automatically" Setting: The user typically has "Refresh using native hooks or polling" enabled in Eclipse, and may or may not have "Build Automatically" enabled.

If code changes don't appear to be taking effect or are getting overwritten:

  1. Ask the user to check if "Build Automatically" is enabled (Project → Build Automatically menu)
  2. If enabled, politely ask them to disable it: "Could you please disable 'Build Automatically' in Eclipse (Project → Build Automatically)?"
  3. Why this matters:
    • When "Build Automatically" is enabled, Eclipse may rebuild files immediately after saving
    • This can conflict with Maven builds, causing files to be overwritten
    • Disabling it gives you more control over when builds occur
    • The user can manually build when needed using Ctrl+B or Project → Build Project

Resolution Strategy: If you encounter situations where your code changes don't appear to be taking effect:

  1. First, ask about Eclipse "Build Automatically" and request it be disabled if enabled
  2. Then run mvn clean install to clear all compiled artifacts and rebuild fresh
  3. This ensures Maven and IDE compiled code are synchronized
  4. Rerun tests after the clean build to verify changes are properly reflected

When to suspect this issue:

  • Tests fail in unexpected ways after code changes
  • Behavior doesn't match recent code modifications
  • Test results seem to reflect old code despite edits
  • Compilation succeeds but runtime behavior is wrong
  • Code changes appear to be getting overwritten or not taking effect

Java Runtime Location: If you can't find Java on the file system using standard commands, look for it in the ~/jdk folder. For example:

  • Java 17 can be found at: ~/jdk/openjdk_17.0.14.0.101_17.57.18_aarch64/bin/java
  • Use this path when you need to run Java commands directly

5. Helper Scripts

Build and Test Script: A reusable Python script is available at scripts/test.py for common Maven operations.

Usage:

# Default: Clean build + run tests
./scripts/test.py

# Build only (skip tests)
./scripts/test.py --build-only
./scripts/test.py -b

# Test only (no build)
./scripts/test.py --test-only
./scripts/test.py -t

# Full build and test (explicit)
./scripts/test.py --full
./scripts/test.py -f

# Verbose output (show full Maven output)
./scripts/test.py --verbose
./scripts/test.py -v

# Help
./scripts/test.py --help
./scripts/test.py -h

What it does:

  • --build-only/-b: Runs mvn clean install -q -DskipTests
  • --test-only/-t: Runs mvn test -q -Drat.skip=true
  • --full/-f (default): Runs both build and test in sequence
  • By default, shows only the last 50 lines of output
  • Use --verbose/-v to see full Maven output

When to use:

  • Instead of manually typing out Maven commands repeatedly
  • When you need to verify both build and tests pass
  • During iterative development to quickly test changes

5.1. Coverage Script

A reusable Python script is available at scripts/coverage.py for checking JaCoCo test coverage on any source file, package, or module.

When asked for current test coverage on a class, package, or module, always use this script rather than manually parsing JaCoCo output.

Usage:

# Coverage for an entire package
./scripts/coverage.py juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/

# Coverage for a single file
./scripts/coverage.py juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java

# Show only lines with missed branches (hide instruction-only misses)
./scripts/coverage.py path/to/file.java --branches

# Re-run tests first to refresh coverage data, then report
./scripts/coverage.py path/to/folder/ --run

What it does:

  • Detects the Maven module automatically from the path
  • Generates a JaCoCo report using the existing juneau-utest/target/jacoco.exec
  • Displays branch and instruction coverage percentages with a progress bar
  • Lists every uncovered line with the number of missed branches/instructions

How coverage data works:

  • The .exec file is produced by juneau-utest during a test run and covers all loaded classes, including those from other modules (e.g. juneau-commons)
  • Use --run when tests have changed since the last run; omit it to reuse existing data for speed
  • Report generation takes ~2 seconds; test execution adds ~10-15 seconds

Trigger phrases: When the user says any of the following, run this script:

  • "what's the coverage on ..."
  • "show coverage for ..."
  • "current test coverage"
  • "check coverage"
  • "coverage report"

5.3. Command Execution Best Practices

Adding Timeouts to Commands:

When running terminal commands that use head, tail, or pipe operations that might hang waiting for input, always wrap them with the timeout command to prevent indefinite hangs:

  1. Use timeout for commands with head/tail:

    • Instead of: command | grep pattern | head -5
    • Use: timeout 30s sh -c 'command | grep pattern | head -5'
  2. Timeout durations:

    • Quick commands (grep, find, etc.): 10-30 seconds
    • Maven commands: 60-120 seconds (or longer for full builds)
    • Adjust based on expected command duration
  3. Why this matters:

    • Commands with head/tail can hang indefinitely if the input stream doesn't close
    • Pipe operations may wait for input that never arrives
    • Timeouts prevent the assistant from getting stuck waiting for commands that will never complete

Examples:

# Quick command with timeout
timeout 10s sh -c 'mvn test-compile 2>&1 | grep "ERROR" | head -5'

# Maven command with longer timeout
timeout 120s sh -c 'mvn clean install 2>&1 | tail -20'

6. Error Handling and Validation

  • Use assertThrowsWithMessage for exception testing
  • Test both valid and invalid scenarios
  • Include proper error messages in assertions
  • Test null parameter validation where applicable

7. Code Coverage Guidelines

  • Aim for 100% instruction coverage on bean classes
  • Use JaCoCo reports to identify missing coverage
  • Focus on methods with 0% coverage first
  • Add comprehensive tests for all code paths
  • Hard-to-Test Code Marking: When a line of code is found to not be fully testable (e.g., requires complex setup, compiler-generated code, or unreachable branches), add a comment // HTT (Hard To Test) on that line to document why it's difficult to test
  • When writing unit tests for new code, always run coverage.py afterward to identify coverage gaps before moving on. Use --run to refresh the .exec data after adding new tests:
    ./scripts/coverage.py path/to/NewFile.java --run
    ./scripts/coverage.py path/to/new/package/ --run
    ./scripts/coverage.py path/to/new/package/ --branches  # show only missed branches

8. File Organization and Naming

  • Follow established file naming conventions
  • Maintain proper package structure
  • Use consistent import organization
  • Follow established class and method naming patterns

9. Documentation Links and References

  • Use hardcoded links to https://juneau.apache.org/docs/topics/
  • Include specification links for external standards where applicable
  • Use proper cross-references with {@link} tags
  • Maintain consistent link formatting

10. Systematic Approach

  • Work through tasks systematically and alphabetically when specified
  • Complete tasks without breaks when requested
  • Verify all changes work correctly
  • Maintain consistency across similar files

11. TODO List Management

  • When the user says "add to TODO" or "add to the TODO list", this refers to the TODO.md file in the /todo folder
  • Do NOT use the in-memory todo list tool for user-requested TODO items
  • Add items directly to the /todo/TODO.md file using the write or search_replace tools
  • Follow the existing format and structure of the TODO.md file
  • TODO Identifiers: When adding new TODO items, assign them a unique "TODO-#" identifier (e.g., "TODO-1", "TODO-2", etc.)
  • TODO References: When the user asks to "fix TODO-X" or "work on TODO-X", they are referring to the specific identifier in the TODO.md file
  • TODO Completion: When TODOs are completed, remove them from the TODO list entirely
  • Plans: Implementation plans (e.g., 1_ini_implementation.md, 2_hjson_implementation.md) are also located in the /todo folder alongside TODO.md

12. Release Notes Management

  • When the user says "add to release notes" or "add this to the release notes", this refers to the release notes in the /juneau-docs directory
  • Do NOT add to the root RELEASE-NOTES.txt file
  • For the current release (9.2.0), add entries to /juneau-docs/9.2.0.md
  • Follow the existing format and structure of the release notes in juneau-docs
  • Use the same formatting style as other entries in the release notes file

13. Fluent Setter Override Formatting

When adding fluent setter overrides to classes:

  • Include blank lines between each method
  • Each override method should be separated by exactly one blank line
  • Example pattern:
    @Override /* Overridden from ParentClass */
    public ChildClass setProperty1(Type value) {
        super.setProperty1(value);
        return this;
    }
    
    @Override /* Overridden from ParentClass */
    public ChildClass setProperty2(Type value) {
        super.setProperty2(value);
        return this;
    }
  • Rationale: Maintains consistency with existing code style and improves readability

properties() Method Formatting

When creating a properties() method that uses the fluent map API with .a() method calls:

  1. Always use Utils.filteredBeanPropertyMap(): Use filteredBeanPropertyMap() to create the map. This method provides sorted, filtered, fluent maps specifically designed for bean property maps.
  2. Wrap in formatter comments: Wrap the entire method contents inside // @formatter:off and // @formatter:on comments to preserve formatting
protected FluentMap<String,Object> properties() {
	// @formatter:off
	return filteredBeanPropertyMap()
		.a("property1", value1)
		.a("property2", value2)
		.a("property3", value3);
	// @formatter:on
}

Rationale:

  • filteredBeanPropertyMap() automatically provides sorted entries, filtered values (removes empty/null values), and a fluent API
  • Ensures consistent ordering for deterministic output
  • Provides a clean, dedicated API for building bean property maps
  • Formatter comments preserve the formatting of fluent method chains and prevent formatters from reformatting the code in ways that reduce readability

Documentation Guidelines for Apache Juneau

This document outlines the documentation conventions, formatting rules, and best practices for the Apache Juneau project.

Javadoc Formatting Standards

HTML Tags and Formatting

Java Variables in Examples:

  • Wrap local Java variables in <jv> tags
  • Example: <jv>json</jv>, <jv>swagger</jv>, <jv>result</jv>

Static Method References:

  • Use <jsm> tags for static method names
  • Example: Json.<jsm>from</jsm>(<jv>x</jv>)

Static Field References:

  • Use <jsf> tags for static field names
  • Example: JsonSerializer.<jsf>DEFAULT</jsf>

Code Comments:

  • Use <jc> tags for code comments
  • Example: <jc>// Serialize using JsonSerializer.</jc>

Class References:

  • Use <jk> tags for class names in text
  • Example: <jk>null</jk>, <jk>String</jk>

Syntax Highlighting Tags (from juneau-code.css)

Java Code Tags:

  • <jc> - Java comment (green)
  • <jd> - Javadoc comment (blue)
  • <jt> - Javadoc tag (blue, bold)
  • <jk> - Java keyword (purple, bold)
  • <js> - Java string (blue)
  • <jf> - Java field (dark blue)
  • <jsf> - Java static field (dark blue, italic)
  • <jsm> - Java static method (italic)
  • <ja> - Java annotation (grey)
  • <jp> - Java parameter (brown)
  • <jv> - Java local variable (brown)

XML Code Tags:

  • <xt> - XML tag (dark cyan)
  • <xa> - XML attribute (purple)
  • <xc> - XML comment (medium blue)
  • <xs> - XML string (blue, italic)
  • <xv> - XML value (black)

JSON Code Tags:

  • <joc> - JSON comment (green)
  • <jok> - JSON key (purple)
  • <jov> - JSON value (blue)

URL Encoding/UON Tags:

  • <ua> - Attribute name (black)
  • <uk> - true/false/null (purple, bold)
  • <un> - Number value (dark blue)
  • <us> - String value (blue)

Manifest File Tags:

  • <mc> - Manifest comment (green)
  • <mk> - Manifest key (dark red, bold)
  • <mv> - Manifest value (dark blue)
  • <mi> - Manifest import (dark blue, italic)

Config File Tags:

  • <cc> - Config comment (green)
  • <cs> - Config section (dark red, bold)
  • <ck> - Config key (dark red)
  • <cv> - Config value (dark blue)
  • <ci> - Config import (dark red, bold, italic)

Special Tags:

  • <c> - Synonym for <code>
  • <dc> - Deleted code (strikethrough)
  • <bc> - Bold code (bold)

Code Block Classes:

  • bcode - Bordered code block
  • bjava - Bordered Java code block
  • bjson - Bordered JSON code block
  • bxml - Bordered XML code block
  • bini - Bordered INI code block
  • buon - Bordered UON code block
  • burlenc - Bordered URL encoding code block
  • bconsole - Bordered console output (black background, yellow text)
  • bschema - Bordered schema code block
  • code - Unbordered code block

Javadoc Structure

Standard Method Javadoc:

/**
 * Brief description of what the method does.
 *
 * <p>
 * Longer description if needed, explaining the purpose and behavior.
 * </p>
 *
 * @param paramName Description of the parameter.
 * @return Description of what is returned.
 * @throws ExceptionType Description of when this exception is thrown.
 */

Property Documentation:

/**
 * The property name.
 *
 * @param value The new value for this property.
 * @return This object.
 */

Parameter Documentation

Standard Parameters:

  • Use value as the parameter name for fluent setters
  • Document parameter types and constraints
  • Specify when parameters can be null

Builder Method Parameter Naming:

  • For single-value setter methods in builder classes, use value as the parameter name
  • This allows field assignment without the this. modifier (e.g., field = value instead of this.field = value)
  • Improves code readability and reduces redundancy

Null Parameter Handling:

/**
 * Sets the property value.
 *
 * @param value The new value. Can be <jk>null</jk> to unset the property.
 * @return This object.
 */

Required Parameters:

/**
 * Sets the property value.
 *
 * @param value The new value. Must not be <jk>null</jk>.
 * @return This object.
 */

Link and Reference Standards

Documentation Links

Juneau Documentation Site:

  • Use hardcoded links to https://juneau.apache.org/docs/topics/
  • Use slug names as topic names
  • Use page titles for anchor text

Examples:

/**
 * <h5 class='section'>See Also:</h5><ul>
 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ModuleName">module-name</a>
 * </ul>
 */

External Specification Links:

/**
 * <a class="doclink" href="https://example.com/specification/#property">property</a> description.
 */

Cross-References

Internal Class References:

  • Use {@link ClassName} for internal class references
  • Use {@link ClassName#methodName} for method references

External References:

  • Use <a class="doclink" href="URL">text</a> for external links
  • Include specification links for external standards where applicable

Code Examples

JSON Serialization Examples

Standard Pattern:

/**
 * <jc>// Serialize using JsonSerializer.</jc>
 * String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>x</jv>);
 *
 * <jc>// Or just use toString() which does the same as above.</jc>
 * String <jv>json</jv> = <jv>x</jv>.toString();
 */

Consistency Requirements:

  • Always include both Json.from() and toString() examples
  • Use consistent variable names (<jv>json</jv>, <jv>x</jv>)
  • Include explanatory comments

Method Usage Examples

Fluent Setter Examples:

/**
 * <jc>// Create a link element</jc>
 * A <jv>link</jv> = a().href("https://example.com").target("_blank");
 */

Builder Pattern Examples:

/**
 * <jc>// Create a Swagger document</jc>
 * Swagger <jv>swagger</jv> = swagger()
 *     .info(info().title("My API").version("1.0"))
 *     .path("/users", pathItem().get(operation().summary("Get users")));
 */

Property Documentation

Bean Properties

Standard Property Documentation:

/**
 * The property description.
 *
 * @param value The new value for this property.
 * @return This object.
 */

Property with External Link Documentation:

/**
 * The <a class="doclink" href="https://example.com/spec#property">property</a> description.
 *
 * @param value A description of the parameter.
 * @return This object.
 */

Class Documentation

Class-Level Javadoc

Standard Structure:

/**
 * Brief description of the class purpose.
 *
 * <h5 class='section'>See Also:</h5><ul>
 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ModuleName">module-name</a>
 * </ul>
 */

Builder Classes:

/**
 * Builder class for creating {@link ClassName} objects.
 *
 * <p>
 * This class provides fluent methods for building complex objects.
 * </p>
 */

Validation and Error Documentation

Parameter Validation

Null Parameter Validation:

/**
 * Sets the property value.
 *
 * @param value The new value. Must not be <jk>null</jk>.
 * @throws IllegalArgumentException If value is <jk>null</jk>.
 * @return This object.
 */

String Validation:

/**
 * Sets the property value.
 *
 * @param value The new value. Must not be <jk>null</jk> or blank.
 * @throws IllegalArgumentException If value is <jk>null</jk> or blank.
 * @return This object.
 */

Strict Mode Documentation

Strict Mode Behavior:

/**
 * Sets the property value.
 *
 * <p>
 * In strict mode, invalid values will throw {@link RuntimeException}.
 * In non-strict mode, invalid values are ignored.
 * </p>
 *
 * @param value The new value.
 * @return This object.
 */

Special Cases

Constructor Documentation

Standard Constructor:

/**
 * Constructor.
 *
 * @param children The child nodes.
 */
public ClassName(Object...children) {

Parameterized Constructor:

/**
 * Constructor.
 *
 * @param param1 Description of first parameter.
 * @param param2 Description of second parameter.
 */
public ClassName(Type1 param1, Type2 param2) {

Override Documentation

Method Overrides:

@Override /* <SimpleClassName> */
public ClassName methodName(Type value) {

Interface Implementations:

@Override /* <SimpleClassName> */
public ClassName methodName(Type value) {

Best Practices

Consistency

  • Use consistent parameter names (value for fluent setters)
  • Use consistent variable names in examples
  • Use consistent link formatting

Completeness

  • Document all public methods and constructors
  • Include parameter types and constraints
  • Include return value descriptions
  • Include exception documentation

Clarity

  • Use clear, concise descriptions
  • Include practical examples
  • Link to relevant specifications
  • Explain complex behavior

Maintenance

  • Keep documentation up-to-date with code changes
  • Use consistent formatting throughout
  • Include cross-references where helpful
  • Document edge cases and special behavior

Common Patterns

Fluent Setter Documentation

/**
 * Sets the property value.
 *
 * @param value The new value for this property.
 * @return This object.
 */
public ClassName property(Type value) {

Collection Setter Documentation

/**
 * Sets the collection of items.
 *
 * @param value The new collection. Can be <jk>null</jk> to unset the property.
 * @return This object.
 */
public ClassName items(Collection<Item> value) {

Builder Method Documentation

/**
 * Creates a new instance.
 *
 * @return A new instance.
 */
public static ClassName create() {

This document serves as the definitive guide for documentation in the Apache Juneau project, ensuring consistency, completeness, and clarity across all documentation.


Unit Testing Guidelines for Apache Juneau

This document outlines the testing conventions, methodologies, and best practices for unit testing in the Apache Juneau project.

Core Testing Conventions

Sequential Single-Letter Label Convention (SSLLC)

Purpose: Provides consistent, readable naming for test data that improves test maintainability and readability.

Rules:

  1. Single values: Use single letters (a, b, c, etc.)
  2. Multiple values on same line: Append numbers (a1, a2, b1, b2, etc.)
  3. New bean instances: Reset labels to 'a' when creating new bean instances
  4. Consistent prefixes: Use consistent prefixes for multiple values on the same line

Examples:

// Single values
var a = swagger();
var b = info();

// Multiple values on same line
var a1 = operation("get", "/users");
var a2 = operation("post", "/users");
var b1 = response("200");
var b2 = response("400");

// New bean instance - reset to 'a'
var a = new Swagger();
var b = a.info();

Deep Property Path Assertion Pattern (DPPAP)

Purpose: Assert on actual nested property values using deep property paths rather than just collection sizes.

Usage: Use assertBean() with deep property paths to verify actual values, not just collection sizes.

Examples:

// Instead of just checking size
assertNotNull(swagger.getPaths());
assertEquals(2, swagger.getPaths().size());

// Use deep property path assertions
assertBean(swagger, "paths{get:/users{summary,operationId},post:/users{summary,operationId}}");

TestUtils Convenience Methods

Purpose: Use TestUtils methods instead of direct serializer/parser calls for consistency and readability.

Methods:

  • TestUtils.json(Object) - Serialize object to JSON string
  • TestUtils.json(String, Class) - Deserialize JSON string to object
  • TestUtils.jsonRoundTrip(Object, Class) - Round-trip serialization/deserialization testing

Examples:

// Instead of direct serializer calls
String json = Json5Serializer.DEFAULT.toString(swagger);
Swagger parsed = Json5Parser.DEFAULT.parse(json, Swagger.class);

// Use TestUtils convenience methods
String json = json(swagger);
Swagger parsed = json(json, Swagger.class);
Swagger roundTrip = jsonRoundTrip(swagger, Swagger.class);

Test Structure Patterns

Test Class Naming Convention

All test methods follow the pattern: LNN_testName where:

  • L is a letter from 'a'-'z' representing the test category
  • NN is a number from "00"-"99" for ordering tests within a category
  • testName is a descriptive name for what the test does

Examples:

  • a01_basicTest() - First test in category 'a'
  • b05_serializationTest() - Fifth test in category 'b'
  • c12_validationTest() - Twelfth test in category 'c'

Simple Test Class Structure

For test classes with a small number of tests:

public class BeanName_Test extends TestBase {
    
    @Test void a01_basicPropertyTest() {
        // Test basic properties
    }
    
    @Test void a02_fluentSetters() {
        // Test fluent setter methods
    }
    
    @Test void b01_serialization() {
        // Test JSON serialization
    }
    
    @Test void b02_deserialization() {
        // Test JSON deserialization
    }
}

Key Points:

  • Tests are grouped by letter prefix (a, b, c, etc.)
  • Each group represents a general test category
  • Tests within a group are numbered sequentially

Complex Test Class Structure

For test classes with large numbers of tests that can be grouped into major categories:

public class BeanName_Test extends TestBase {
    
    @Nested class A_basicTests extends TestBase {
        @Test void a01_properties() {
            // Test bean properties using varargs setters
            // Use SSLLC naming convention
            // Use DPPAP for assertions
        }
        
        @Test void a02_fluentSetters() {
            // Test fluent setter chaining
        }
    }
    
    @Nested class B_serialization extends TestBase {
        @Test void b01_toJson() {
            // Test JSON serialization
            // Use TestUtils convenience methods
        }
        
        @Test void b02_fromJson() {
            // Test JSON deserialization
        }
    }
    
    @Nested class C_extraProperties extends TestBase {
        @Test void c01_dynamicProperties() {
            // Test set(String, Object) method
            // Use set(String, Object) for ALL the same properties as A_basicTests
            // Match values from A_basicTests exactly
        }
    }
    
    @Nested class D_additionalMethods extends TestBase {
        @Test void d01_collectionSetters() {
            // Test setX(Collection<X>) methods
        }
        
        @Test void d02_varargAdders() {
            // Test addX(X...) methods
        }
    }
}

Key Points:

  • Use @Nested inner classes for major test categories
  • Each nested class name follows the pattern: L_categoryName where L is an uppercase letter (A, B, C, etc.)
  • Tests within nested classes use l##_testName pattern where l is the lowercase letter matching the nested class (a, b, c, etc.) and ## is an incrementing number (01, 02, 03, etc.)
  • The letter in the nested class name matches the letter in the test method names (uppercase for class, lowercase for methods)
  • Each nested class extends TestBase to inherit test utilities and setup

Common Test Categories

While not prescriptive, common test category prefixes include:

  • a: Basic tests (properties, constructors, basic functionality)
  • b: Serialization/deserialization tests
  • c: Extra/dynamic properties tests
  • d: Additional methods tests
  • e: Validation/strict mode tests
  • f: Reference resolution tests (where applicable)

Choose the structure (Simple vs Complex) based on:

  • Simple: < 20 tests, or tests don't naturally group into major categories
  • Complex: > 20 tests, especially when tests group into distinct functional areas

Assertion Patterns

assertBean Usage

Basic Usage:

assertBean(bean, "property1,property2,property3");

Deep Property Paths:

assertBean(bean, "paths{get:/users{summary,operationId}}");

Collection Assertions:

// Use # notation for uniform collections
assertBean(bean, "parameters{#{in,name}}");

// Use explicit indexing for non-uniform collections
assertBean(bean, "parameters{0{in,name},1{in,name}}");

Map Assertions:

// Use assertMap for map entries
assertMap(map, "key1=value1", "key2=value2");

Exception Testing

Use assertThrowsWithMessage:

assertThrowsWithMessage(IllegalArgumentException.class, 
    "Parameter 'name' cannot be null", 
    () -> bean.setName(null));

Bean Testing Patterns

General Bean Testing

Property Coverage: Ensure A_basicTests covers all bean properties and C_extraProperties covers the same properties using the set() method.

Getter/Setter Variants: Test both collection variants and varargs variants where applicable.

Fluent Setter Testing

Parameter Naming: All fluent setters should use value as the parameter name for consistency.

Method Chaining: Test fluent setter chaining:

var result = bean
    .property1("value1")
    .property2("value2")
    .property3("value3");

Collection Property Method Consistency

Rule: All collection bean properties should have exactly 4 methods:

  1. setX(X...) - varargs setter
  2. setX(Collection<X>) - Collection setter
  3. addX(X...) - varargs adder
  4. addX(Collection<X>) - Collection adder

Examples:

// For a tags property of type Set<String>:
public Bean setTags(String...value) { ... }
public Bean setTags(Collection<String> value) { ... }
public Bean addTags(String...values) { ... }
public Bean addTags(Collection<String> values) { ... }

Varargs vs Collection Setter Testing

Rule: When a bean has both varargs and Collection setter methods for the same property:

  • A_basicTests: Use the varargs version (e.g., setTags("tag1", "tag2"))
  • D_additionalMethods: Test the Collection version (e.g., setTags(list("tag1", "tag2")))

Examples:

// A_basicTests - use varargs
.setTags("tag1", "tag2")
.setConsumes(MediaType.of("application/json"))

// D_additionalMethods - test Collection version
.setTags(list("tag1", "tag2"))
.setConsumes(list(MediaType.of("application/json"), MediaType.of("application/xml")))

D_additionalMethods Test Structure: This test class should contain three tests:

  1. d01_collectionSetters: Tests setX(Collection<X>) methods

    @Test void d01_collectionSetters() {
        var x = bean()
            .setTags(list("tag1", "tag2"))
            .setConsumes(list(MediaType.of("application/json"), MediaType.of("application/xml")));
        
        assertBean(x,
            "tags,consumes",
            "[tag1,tag2],[application/json,application/xml]"
        );
    }
  2. d02_varargAdders: Tests addX(X...) methods - each method should be called twice

    @Test void d02_varargAdders() {
        var x = bean()
            .addTags("tag1")
            .addTags("tag2")
            .addConsumes(MediaType.of("application/json"))
            .addConsumes(MediaType.of("application/xml"));
        
        assertBean(x,
            "tags,consumes",
            "[tag1,tag2],[application/json,application/xml]"
        );
    }
  3. d03_collectionAdders: Tests addX(Collection<X>) methods - each method should be called twice

    @Test void d03_collectionAdders() {
        // Note: Collection versions of addX methods exist but are difficult to test
        // due to Java method resolution preferring varargs over Collection
        // For now, we test the basic functionality with varargs versions
        var x = bean();
        
        // Test that the addX methods work by calling them multiple times
        x.addTags("tag1");
        x.addTags("tag2");
        x.addConsumes(MediaType.of("application/json"));
        x.addConsumes(MediaType.of("application/xml"));
        
        assertBean(x,
            "tags,consumes",
            "[tag1,tag2],[application/json,application/xml]"
        );
    }

In all cases, assertBean should be used to validate results.

Code Coverage Guidelines

Target Coverage

  • Bean Classes: Aim for 100% instruction coverage
  • UI Classes: Can be excluded from coverage targets
  • Builder Classes: Include comprehensive tests for all builder methods

Coverage Analysis

  • Use coverage.py to identify missing coverage — always run it after writing unit tests for new code:
    ./scripts/coverage.py path/to/NewFile.java --run
    ./scripts/coverage.py path/to/new/package/ --run
    ./scripts/coverage.py path/to/new/package/ --branches
  • Focus on methods with 0% coverage first
  • Add tests for uncovered code paths
  • Verify coverage improvements after adding tests

Best Practices

Test Data Management

  • Use SSLLC for consistent test data naming
  • Reset labels when creating new bean instances
  • Use meaningful test data that represents real-world scenarios

Assertion Strategy

  • Prefer assertBean() over individual property assertions
  • Use deep property paths for comprehensive validation
  • Test both positive and negative scenarios

Code Organization

  • Group related tests in logical test methods
  • Use descriptive test method names
  • Follow consistent test structure across all test classes

Test Helper Class Naming Convention

Rule: For test helper classes that are only used by one test, prefix the class name with the test method prefix and move it immediately above the test that uses it.

Naming Pattern: LNN_ClassName where:

  • L is the uppercase letter matching the test method prefix (A, B, C, etc.)
  • NN is the two-digit number matching the test method number (01, 02, 03, etc.)
  • ClassName is the descriptive class name

Placement: Move the helper class definition to immediately above the test method that uses it.

Examples:

// WRONG - Generic name, defined far from usage
public static class BeanWithDeprecatedGetInstance {
    // ...
}

@Test
void c03_createBeanIgnoresDeprecatedGetInstance() {
    var bean = bc(BeanWithDeprecatedGetInstance.class).run();
    // ...
}

// CORRECT - Prefixed with test name, placed above test
@Test
void c03_createBeanIgnoresDeprecatedGetInstance() {
    // Bean with both deprecated and valid getInstance methods
    public static class C03_BeanWithDeprecatedGetInstance {
        private static final C03_BeanWithDeprecatedGetInstance INSTANCE = new C03_BeanWithDeprecatedGetInstance();
        // ...
    }

    var bean = bc(C03_BeanWithDeprecatedGetInstance.class).run();
    // ...
}

Rationale:

  • Makes it immediately clear which test uses the helper class
  • Reduces namespace pollution by scoping helper classes to their tests
  • Improves code locality by keeping related code together
  • Makes it easier to identify unused helper classes

When to Apply:

  • Only apply this convention to helper classes used by a single test
  • Classes used by multiple tests should keep their generic names
  • Classes used across nested test classes should remain at the nested class level

Test State Capture Utilities

Purpose: Use proper utility classes instead of arrays for capturing mutable state in lambdas and test scenarios.

Rules:

  1. Use Flag for boolean state: Instead of new boolean[]{false}, use Flag.create() for capturing boolean flags in lambdas and test hooks.
  2. Use IntegerValue for integer counters: Instead of new int[]{0}, use IntegerValue.create() for capturing integer counts in lambdas and test hooks.

Examples:

// WRONG - Using arrays for state capture
var hookCalled = new boolean[]{false};
bc(SimpleBean.class)
    .postCreateHook(b -> hookCalled[0] = true)
    .run();
assertTrue(hookCalled[0]);

var callCount = new int[]{0};
bc(SimpleBean.class)
    .postCreateHook(b -> callCount[0]++)
    .run();
assertEquals(1, callCount[0]);

// CORRECT - Using Flag and IntegerValue
var hookCalled = Flag.create();
bc(SimpleBean.class)
    .postCreateHook(b -> hookCalled.set())
    .run();
assertTrue(hookCalled.isSet());

var callCount = IntegerValue.create();
bc(SimpleBean.class)
    .postCreateHook(b -> callCount.increment())
    .run();
assertEquals(1, callCount.get());

Flag Methods:

  • Flag.create() - Creates a flag initialized to false
  • flag.set() - Sets the flag to true
  • flag.isSet() - Returns true if flag is set
  • flag.isUnset() - Returns true if flag is not set
  • flag.setIf(condition) - Sets flag if condition is true

IntegerValue Methods:

  • IntegerValue.create() - Creates an integer initialized to 0
  • counter.increment() - Increments by 1
  • counter.get() - Returns the current value
  • counter.getAndIncrement() - Returns current value then increments
  • counter.incrementAndGet() - Increments then returns new value

Rationale:

  • Provides cleaner, more readable code than array access patterns
  • Better type safety and API design
  • Consistent with Apache Juneau utility class patterns
  • Makes test code more maintainable and easier to understand

Documentation

  • Include javadoc for test methods explaining their purpose
  • Document any special test scenarios or edge cases
  • Keep test code readable and maintainable

Common Patterns

Round-trip Testing

@Test void B_serialization() {
    var original = createTestBean();
    var roundTrip = jsonRoundTrip(original, BeanClass.class);
    assertBean(original, roundTrip);
}

Strict Mode Testing

@Test void E_strictMode() {
    // Test invalid value with strict mode
    assertThrowsWithMessage(RuntimeException.class, 
        "Invalid value", 
        () -> bean.setProperty("invalid"));
    
    // Test valid value with strict mode
    assertDoesNotThrow(() -> bean.setProperty("valid"));
}

Collection Testing

@Test void a08_otherGettersAndSetters() {
    var a = bean.addItems("item1", "item2");
    assertBean(a, "items{0=item1,1=item2}");
    
    var b = bean.setItems(Arrays.asList("item3", "item4"));
    assertBean(b, "items{0=item3,1=item4}");
}

Serializer and Parser Implementation Rules

Adding Settings to Serializers/Parsers

When adding a new setting (configuration property) to a serializer or parser, follow these steps:

1. Add Field to Builder Class

public static class Builder extends XmlSerializer.Builder {
    String textNodeDelimiter;  // Add the field
}

2. Initialize in Constructor

protected Builder() {
    textNodeDelimiter = env("XmlSerializer.textNodeDelimiter", "");  // Set default
}

3. Add to Copy Constructors

protected Builder(XmlSerializer copyFrom) {
    super(copyFrom);
    textNodeDelimiter = copyFrom.textNodeDelimiter;  // Copy from serializer
}

protected Builder(Builder copyFrom) {
    super(copyFrom);
    textNodeDelimiter = copyFrom.textNodeDelimiter;  // Copy from builder
}

4. Add Setter Method

public Builder textNodeDelimiter(String value) {
    textNodeDelimiter = value == null ? "" : value;
    return this;
}

5. CRITICAL: Update hashKey() Method

This is essential to prevent caching issues where different configurations would incorrectly share the same cached instance:

@Override /* Context.Builder */
public HashKey hashKey() {
    return HashKey.of(
        super.hashKey(),
        addBeanTypesXml,
        addNamespaceUrisToRoot,
        // ... other fields ...
        textNodeDelimiter  // ADD NEW SETTING HERE
    );
}

Why this is critical: Serializers and parsers use caching based on the hash key. If a new setting is not included in the hash key, two builders with different values for that setting will hash to the same key and incorrectly use the same cached instance, causing the second configuration to be ignored.

6. Add Field to Main Class

public class XmlSerializer extends WriterSerializer {
    final String textNodeDelimiter;  // Add to main class
    
    public XmlSerializer(Builder builder) {
        super(builder);
        textNodeDelimiter = builder.textNodeDelimiter;  // Initialize from builder
    }
}

7. Pass to Session (if needed)

If the setting needs to be accessed during serialization:

public class XmlSerializerSession extends WriterSerializerSession {
    private final String textNodeDelimiter;
    
    protected XmlSerializerSession(Builder builder) {
        super(builder);
        textNodeDelimiter = ctx.textNodeDelimiter;  // Get from context
    }
}

8. Override in Subclasses (if needed)

If the serializer has subclasses (e.g., HtmlSerializer extends XmlSerializer), override the setter to maintain fluent API:

// In HtmlSerializer.Builder
@Override
public Builder textNodeDelimiter(String value) {
    super.textNodeDelimiter(value);  // Call parent
    return this;  // Return correct type
}

Common Pitfalls

  • Forgetting to add the setting to hashKey() - This causes caching bugs where different configurations share the same cached instance
  • ❌ Not copying the field in all copy constructors
  • ❌ Not overriding setter methods in subclass builders
  • ❌ Not passing the setting to the session if it's needed during serialization

This document serves as the definitive guide for unit testing in the Apache Juneau project, ensuring consistency, maintainability, and comprehensive test coverage.


Release Notes Guidelines

Location

When asked to "add to the release notes", this refers to the current release file located at:

  • /docs/pages/release-notes/<VERSION>.md
  • Current version: 9.2.1
  • Current file: /docs/pages/release-notes/9.2.1.md

Structure

Release notes are organized into two main sections:

  1. Top-level major changes - High-level overview at the beginning of the file listing significant changes
  2. Per-module updates - Detailed changes organized by module (similar to 9.0.0.md structure):
    • juneau-marshall
    • juneau-rest-common
    • juneau-rest-server
    • juneau-rest-client
    • juneau-dto
    • juneau-microservice
    • juneau-examples
    • Other modules as applicable

Format

Each section should include:

  • New features
  • Bug fixes
  • Breaking changes
  • Deprecations
  • Performance improvements
  • Documentation updates
  • API changes

Process

  1. Read the current release notes file (9.2.0.md) to understand the existing structure
  2. Determine if the change is a major change (top-level) or module-specific
  3. Add new entries under the appropriate section and module
  4. Use clear, concise descriptions with code examples where helpful
  5. Include issue/PR references where applicable
  6. Maintain consistent formatting with existing entries (see 9.0.0.md for reference)