Skip to main content

Release 9.2.0

Date: 01/05/2026

Juneau 9.2.0 is a minor release focused on enhancements and bug fixes.

Major changes include:

  • New Module: Introduced juneau-shaded with five shaded (uber) JAR artifacts for simplified dependency management, especially useful for Bazel
  • @Schema Annotation upgraded to JSON Schema Draft 2020-12 with 18 new properties, while maintaining full backward compatibility with Draft 04
  • HttpPartFormat Enhancement: Added 18 new format types (email, hostname, UUID, URI, IPv4/IPv6, etc.) with comprehensive validation
  • Jakarta Bean Validation Integration: Automatic detection and processing of Jakarta Validation constraints (@NotNull, @Email, @Size, etc.) with zero dependencies
  • CSV Serializer Swap Support: Added full object swap support to CsvSerializer, enabling date formatting, enum customization, and complex object transformations
  • Remote Proxy Default Values: Added def attribute to all HTTP part annotations (@Header, @Query, @FormData, @Path, @Content) for specifying method-level default values
  • JSON Schema beans upgraded to Draft 2020-12 specification with backward compatibility for Draft 04
  • Comprehensive enhancements to HTML5 beans with improved javadocs and HtmlBuilder integration
  • Standardized license headers across all Java files
  • Enhanced test coverage for OpenAPI 3.0 and Swagger 2.0 beans
  • Removed final modifiers from many classes to improve testability with Mockito and other mocking frameworks
  • Improved RestRequest and RestResponse to work cleanly with try-with-resources blocks
  • New Module: Introduced juneau-bct (Bean-Centric Testing) with fluent APIs for comprehensive bean testing
  • Deprecated and removed juneau-all in favor of juneau-shaded-all

juneau-dto

juneau-bean-jsonschema

  • JSON Schema Draft 2020-12 Compliance: The JsonSchema bean has been upgraded to support JSON Schema Draft 2020-12 while maintaining backward compatibility with Draft 04.

    New properties added:

    • $id (replaces id)
    • $defs (replaces definitions)
    • const - Single constant value validation
    • examples - Array of example values
    • if, then, else - Conditional schema validation
    • readOnly, writeOnly - Property access control
    • contentMediaType, contentEncoding - String content metadata
    • prefixItems - Array prefix validation
    • unevaluatedItems, unevaluatedProperties - Unevaluated content handling
    • dependentSchemas, dependentRequired - Property dependencies

    Modified properties:

    • exclusiveMaximum / exclusiveMinimum - Now support numeric values (Draft 2020-12) in addition to boolean (Draft 04)
    • enum - Changed to List<Object> to support any type
  • Improved Maintainability: Removed explicit @Bean(properties) annotation in favor of automatic alphabetical property discovery with JsonSerializer.DEFAULT_SORTED.

  • Enhanced Documentation:

    • Updated package-info with comprehensive usage examples
    • Added migration guide from Draft 04 to Draft 2020-12
    • Merged package2.html content into package-info.java for consolidated documentation
  • Bug Fixes:

    • Added @BeanIgnore to internal setters (setName, setSchemaMap) to prevent serialization issues in HTML round-trip tests
    • Fixed fallback logic for deprecated properties to prevent double serialization

juneau-bean-openapi3

  • Enhanced Test Coverage: Achieved near 100% instruction coverage for all OpenAPI 3.0 beans

    • Added comprehensive tests for MediaType, Link, Example, RequestBody, Header, Encoding, SecurityScheme, OAuthFlows, OAuthFlow, ServerVariable, Discriminator, Xml, ExternalDocumentation
    • Added validation tests for required fields and strict mode behavior
    • Added reference resolution tests (resolveRefs()) for all applicable beans
  • Collection Method Consistency: Ensured all collection properties have the standard 4-method pattern:

    • setX(X...) - varargs setter
    • setX(Collection<X>) - Collection setter
    • addX(X...) - varargs adder
    • addX(Collection<X>) - Collection adder
  • Bug Fixes:

    • Fixed OpenApi.resolveRefs() to properly handle servers array references
    • Added null-safety checks in various resolveRefs() implementations

juneau-bean-swagger

  • Enhanced Test Coverage: Achieved near 100% instruction coverage for all Swagger 2.0 beans

    • Added comprehensive tests for ParameterInfo, HeaderInfo, Tag, SecurityScheme, Xml, ExternalDocumentation
    • Added validation tests for required fields and strict mode behavior
    • Added reference resolution tests for all applicable beans
  • Collection Method Consistency: Standardized collection property methods across all beans

  • Bug Fixes:

    • Fixed Swagger.resolveRefs() to properly resolve references in nested structures
    • Added null-safety checks in path and definition resolution

juneau-bean-html5

  • @Html(style) Annotation: Added style() property to the @Html annotation for applying CSS styles to HTML elements.

    • Provides a simpler alternative to @Html(render=...) when only CSS styling is needed
    • Example: @Html(style="white-space:normal;min-width:200px")
    • Style strings are applied directly to the HTML element containing the bean property value
    • If both style() and render() are specified, the render takes precedence
  • Comprehensive Javadoc Enhancements: Updated javadocs for all 95 HTML5 bean classes with:

    • Detailed descriptions of each HTML element's purpose and usage
    • Complete attribute documentation with enumerated values and constraints
    • Multiple practical usage examples
    • Links to W3C HTML5 specification for each element
  • HtmlBuilder Integration: Added HtmlBuilder creator method documentation using javatree format:

    /**
    * <p>
    * The following convenience methods are provided for constructing instances of this bean:
    * <ul class='javatree'>
    * <li class='jc'>{@link HtmlBuilder}
    * <ul class='javatree'>
    * <li class='jm'>{@link HtmlBuilder#a() a()}
    * <li class='jm'>{@link HtmlBuilder#a(Object, Object...) a(Object, Object...)}
    * </ul>
    * </ul>
    * </p>
    */
  • Example Refactoring: Replaced constructor-based examples with HtmlBuilder methods where applicable (examples with children defined)

  • Documentation Consistency: Standardized formatting and structure across all HTML5 bean classes

juneau-core

Multi-Key Cache and Concurrent Map Classes

Class Renaming for Consistency:

The multi-key concurrent map classes have been renamed to follow standard Java naming conventions:

  • Concurrent2KeyHashMapConcurrentHashMap2Key
  • Concurrent3KeyHashMapConcurrentHashMap3Key
  • Concurrent4KeyHashMapConcurrentHashMap4Key
  • Concurrent5KeyHashMapConcurrentHashMap5Key

ConcurrentHashMapXKey Refactoring:

The ConcurrentHashMapXKey classes have been simplified to be pure multi-key concurrent hash maps:

  • Removed disabled and supplier parameters from constructors
  • Now extend ConcurrentHashMap<TupleX<K1,K2,...>,V> directly
  • Added null key validation (throws IllegalArgumentException for null keys)
  • Simplified get() and put() methods to use Tuple classes directly

New Cache Classes:

Introduced dedicated caching classes that separate caching concerns from the base concurrent maps:

  • Cache<K,V> extends ConcurrentHashMap<K,V> - Single-key cache
  • Cache2<K1,K2,V> extends ConcurrentHashMap2Key<K1,K2,V> - Two-key cache
  • Cache3<K1,K2,K3,V> extends ConcurrentHashMap3Key<K1,K2,K3,V> - Three-key cache
  • Cache4<K1,K2,K3,K4,V> extends ConcurrentHashMap4Key<K1,K2,K3,K4,V> - Four-key cache
  • Cache5<K1,K2,K3,K4,K5,V> extends ConcurrentHashMap5Key<K1,K2,K3,K4,K5,V> - Five-key cache

Cache Features:

  • Builder pattern with fluent API
  • disableCaching() - Bypass cache and always invoke supplier
  • maxSize(int) - Automatic eviction when size exceeds threshold
  • logOnExit() - Log cache statistics on JVM shutdown
  • supplier(FunctionX<K1,...,V>) - Default supplier for cache misses
  • get(K1,...) - Retrieve using default supplier
  • get(K1,..., Supplier<V>) - Retrieve with override supplier
  • getCacheHits() - Track cache hit statistics
  • Null key validation for all key components

Example Usage:

// Two-key cache for class metadata
Cache2<ClassLoader,Class<?>,ClassMeta> metaCache = Cache2
.of(ClassLoader.class, Class.class, ClassMeta.class)
.maxSize(500)
.supplier((cl, clazz) -> computeMeta(cl, clazz))
.build();

ClassMeta meta = metaCache.get(classLoader, MyClass.class);

// Three-key cache for translations
Cache3<String,String,Integer,String> i18nCache = Cache3
.of(String.class, String.class, Integer.class, String.class)
.maxSize(1000)
.logOnExit()
.supplier((lang, country, msgId) -> loadTranslation(lang, country, msgId))
.build();

String greeting = i18nCache.get("en", "US", 1); // "Hello"

Migration Notes:

  • Code using ConcurrentXKeyHashMap with disabled/supplier should migrate to CacheX
  • Code using ConcurrentXKeyHashMap without caching features requires only class name updates
  • Null keys are no longer allowed (previously bypassed cache, now throws IllegalArgumentException)

Breaking Changes:

  • ConcurrentXKeyHashMap constructors no longer accept disabled or supplier parameters
  • Passing null keys to get() or put() now throws IllegalArgumentException instead of bypassing cache

License Header Standardization

  • Java Files: Replaced non-standard comment-based license headers with the standard Apache License block comment format across all Java files
  • Properties Files: Updated license headers in .properties files to use the standard format
  • XML Files: Standardized XML license headers with proper comment syntax
  • Package Statement Fixes: Corrected package statements that were in unusual positions after license header changes

StringUtils Improvements

  • Input Validation for Hex Methods: Added negative number validation to StringUtils.toHex4() and StringUtils.toHex8() methods to prevent invalid input processing:

    • toHex4(int) now throws NumberFormatException for negative numbers
    • toHex8(long) now throws NumberFormatException for negative numbers
    • Both methods provide clear error messages: "toHex4/toHex8 can only be used on non-negative numbers"
    • Maintains consistency with existing toHex2(int) method which already had similar validation
    • Comprehensive unit tests added for both methods covering positive numbers, edge cases, and negative number validation
  • State Machine Standardization: Converted remaining integer-based state machine constants to use StateEnum for improved type safety:

    • Updated StringUtils.replaceVars() method to use StateEnum.S1 and StateEnum.S2
    • Updated Utils.parseKeyValuePairs() method to use StateEnum.S1 and StateEnum.S2
    • Updated UonParserSession.parseObject() method to use StateEnum.S1 through StateEnum.S4
    • Updated UrlEncodingParserSession methods to use StateEnum.S1 through StateEnum.S4
    • Added missing StateEnum import to UrlEncodingParserSession
    • All state machines now use consistent enum-based state management
  • Jakarta XML Bind Dependency Elimination: Replaced jakarta.xml.bind-api dependency with modern Java time APIs:

    • Updated StringUtils.toIsoDate(Calendar) to use ZonedDateTime and DateTimeFormatter.ISO_LOCAL_DATE
    • Updated StringUtils.toIsoDateTime(Calendar) to use ZonedDateTime and DateTimeFormatter.ISO_OFFSET_DATE_TIME
    • Removed import jakarta.xml.bind.*; from StringUtils.java
    • Added import java.time.*; and import java.time.format.*; for modern time API support
    • Maintains identical output format and behavior while eliminating external dependency
    • Improved timezone preservation in serialized date-time values
    • Enhanced performance with modern Java 8+ time APIs
  • Code Cleanup: Removed commented-out code blocks to improve code maintainability:

    • Removed entire commented-out getResponseBeanMeta() method from RestContext.java
    • Removed commented-out findClasses() method from BeanMeta.java
    • Removed commented-out control flow block from JettyMicroservice.java
    • Removed debug if(false) condition from ParserReader.java
    • Removed commented-out method call from HtmlParserSession.java
    • Removed commented-out test assertion from ThirdPartyProxyResource.java
    • Removed commented-out list2() method from CollectionUtils.java
    • Preserved test method section headers as they serve as useful documentation

Mockito Compatibility

  • Removed final Modifiers: Removed final modifiers from many classes across the framework to improve testability with Mockito and other mocking frameworks that require non-final classes. This change makes it significantly easier to write unit tests for code that uses Juneau APIs.

juneau-marshall

@Schema Annotation - JSON Schema Draft 2020-12 Support

  • Draft 2020-12 Compliance: The @Schema annotation has been upgraded to support JSON Schema Draft 2020-12 while maintaining full backward compatibility with Draft 04 (used by Swagger 2.0 and OpenAPI 3.0).

    New Draft 2020-12 Properties:

    • $id() - Schema URI identifier (e.g., "https://example.com/schemas/user")
    • _const() - Constant value validation (value must exactly match)
    • examples() - Array of example values for documentation
    • $comment() - Comments for schema authors (not for end users)
    • deprecatedProperty() - Boolean flag to mark schema/property as deprecated
    • exclusiveMaximumValue() / exclusiveMinimumValue() - Numeric exclusive bounds (replaces boolean flags)
    • contentMediaType() - MIME type for string contents (e.g., "application/json")
    • contentEncoding() - Encoding for string contents (e.g., "base64")
    • prefixItems() - Tuple validation for array prefixes
    • unevaluatedItems() - Additional validation for unevaluated array items
    • unevaluatedProperties() - Additional validation for unevaluated object properties
    • dependentSchemas() - Conditional subschemas based on property presence
    • dependentRequired() - Conditionally required properties
    • _if() / _then() / _else() - Conditional schema validation
    • $defs() - Reusable schema definitions (replaces definitions)

    Example Usage:

    @Schema(
    type="integer",
    _const="FIXED_VALUE",
    examples={"100", "200", "300"},
    $comment="Internal use only",
    deprecatedProperty=true,
    exclusiveMaximumValue="1000",
    exclusiveMinimumValue="0"
    )
    public int legacyField;
  • Backward Compatibility:

    • Old boolean exclusiveMaximum / exclusiveMinimum properties still work but are deprecated
    • New numeric exclusiveMaximumValue / exclusiveMinimumValue take precedence when both are specified
    • All Draft 04 properties continue to function as before

    Migration Example:

    // Draft 04 style (still works, but deprecated)
    @Schema(
    type="integer",
    exclusiveMaximum=true, // deprecated
    maximum="100",
    exclusiveMinimum=true, // deprecated
    minimum="0"
    )

    // Draft 2020-12 style (recommended)
    @Schema(
    type="integer",
    exclusiveMaximumValue="100", // 0 < x < 100 (boundaries excluded)
    exclusiveMinimumValue="0"
    )
  • Enhanced Validation:

    • Added const validation in HttpPartSchema - values must exactly match the constant
    • Updated exclusiveMaximum / exclusiveMinimum validation to support both boolean flags (Draft 04) and numeric values (Draft 2020-12)
    • Proper error messages for all new validation types
  • Comprehensive Test Coverage: Added 38 new unit tests covering:

    • All 18 new Draft 2020-12 properties
    • Schema annotation building and processing
    • JSON schema generation
    • Runtime validation (input and output)
    • Backward compatibility scenarios
    • Precedence rules (new vs old style)

HttpPartFormat - JSON Schema/OpenAPI Format Types

  • Complete Format Coverage: Added 18 new format types to the HttpPartFormat enum to align with JSON Schema Draft 2020-12 and OpenAPI 3.x specifications:

    Email Formats:

    • EMAIL - Email address (RFC 5321)
    • IDN_EMAIL - Internationalized email address (RFC 6531)

    Hostname Formats:

    • HOSTNAME - Internet host name (RFC 1123)
    • IDN_HOSTNAME - Internationalized host name (RFC 5890)

    IP Address Formats:

    • IPV4 - IPv4 address (RFC 2673)
    • IPV6 - IPv6 address (RFC 4291)

    URI/IRI Formats:

    • URI - Universal Resource Identifier (RFC 3986)
    • URI_REFERENCE - URI Reference (RFC 3986)
    • IRI - Internationalized Resource Identifier (RFC 3987)
    • IRI_REFERENCE - IRI Reference (RFC 3987)

    Other Formats:

    • UUID - Universally Unique Identifier (RFC 4122)
    • URI_TEMPLATE - URI Template (RFC 6570)
    • JSON_POINTER - JSON Pointer (RFC 6901)
    • RELATIVE_JSON_POINTER - Relative JSON Pointer
    • REGEX - Regular expression (ECMA-262)
    • DURATION - Duration (RFC 3339 Appendix A / ISO 8601)
    • TIME - Time (RFC 3339)
    • DATE_TIME_ZONE - Date and time with time zone (RFC 3339)
  • Comprehensive Format Validation: Added automatic validation for all format types in HttpPartSchema:

    • Email Validation: Validates email format according to RFC 5321 (basic) and RFC 6531 (internationalized)
    • Hostname Validation: Validates hostnames per RFC 1123, supporting both ASCII and internationalized domain names
    • IP Address Validation: Full validation for both IPv4 (dotted decimal) and IPv6 (colon-separated hex) formats
    • URI/IRI Validation: Validates URIs and IRIs, including relative references, using Java's java.net.URI parser
    • UUID Validation: Standard UUID format validation (8-4-4-4-12 hex digits)
    • Date/Time Validation: RFC 3339 compliant validation with relaxed patterns to accommodate various serialization formats
    • Regular Expression Validation: Validates regex patterns can be compiled
    • Duration Validation: ISO 8601 duration format validation (e.g., P3Y6M4DT12H30M5S)

    Smart Validation Logic:

    • Skips validation for literal "null" strings (used in serialization)
    • Treats transformation formats (BYTE, BINARY, BINARY_SPACED) as hints, not validation constraints
    • Uses relaxed patterns for dates/times to accommodate various serialization formats
    • Gracefully handles edge cases and malformed inputs

    Example Usage:

    @Query(name="email", schema=@Schema(format="email"))
    public String email; // Validated as email format

    @Query(name="website", schema=@Schema(format="uri"))
    public String website; // Validated as URI

    @Query(name="id", schema=@Schema(format="uuid"))
    public String id; // Validated as UUID

    Validation Points:

    • Format validation occurs in both validateInput() (for incoming string values) and validateOutput() (for serialized objects)
    • Validation failures throw SchemaValidationException with detailed error messages
    • Format validation integrates seamlessly with existing pattern, min/max length, and other validations

Jakarta Bean Validation Integration

  • Reflective Jakarta Validation Support: HttpPartSchema now automatically detects and processes Jakarta Bean Validation constraints without requiring a direct dependency on jakarta.validation-api:

    Supported Constraints:

    • @NotNullrequired(true)
    • @Size(min=X, max=Y)minLength(X), maxLength(X), minItems(X), maxItems(Y)
    • @Min(value)minimum(value)
    • @Max(value)maximum(value)
    • @Pattern(regexp)pattern(regexp)
    • @Emailformat(EMAIL) with automatic email validation
    • @Positiveminimum(0) + exclusiveMinimum(true)
    • @PositiveOrZerominimum(0)
    • @Negativemaximum(0) + exclusiveMaximum(true)
    • @NegativeOrZeromaximum(0)
    • @NotEmptyrequired(true) + minLength(1) + minItems(1)
    • @NotBlankrequired(true) + minLength(1) + pattern(".*\\S.*")
    • @DecimalMin(value, inclusive)minimum(value) with optional exclusiveMinimum
    • @DecimalMax(value, inclusive)maximum(value) with optional exclusiveMaximum

    Example Usage:

    import jakarta.validation.constraints.*;

    public class UserInput {
    @NotNull
    @Email
    @Size(max=255)
    private String email;

    @NotBlank
    @Size(min=8, max=100)
    @Pattern(regexp="^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).+$")
    private String password;

    @Positive
    @Max(150)
    private Integer age;

    @DecimalMin(value="0.0", inclusive=false)
    @DecimalMax(value="999.99")
    private BigDecimal price;
    }

    // HttpPartSchema automatically detects these constraints and applies them
    HttpPartSchema schema = HttpPartSchema.create()
    .apply(field.getAnnotation(NotNull.class))
    .apply(field.getAnnotation(Email.class))
    .apply(field.getAnnotation(Size.class))
    .build();

    // Validation now includes email format, required, and length checks
    schema.validateInput(value);

    Implementation Details:

    • Uses pure reflection to detect annotations starting with jakarta.validation.constraints.
    • No compile-time or runtime dependency on Jakarta Validation API in production code
    • Gracefully ignores unknown or unsupported constraint annotations
    • Maps Jakarta Validation semantics to OpenAPI/JSON Schema equivalents
    • Test module includes jakarta.validation-api as a test dependency for comprehensive testing
  • Enhanced OpenAPI Compatibility: Jakarta Validation constraints are now seamlessly translated to OpenAPI schema properties, enabling automatic documentation generation and client-side validation in tools like Swagger UI.

@PathRemainder Annotation

  • New Annotation: Added @PathRemainder as an intuitive shortcut for @Path("/*") to capture the path remainder in REST endpoints.

    Features:

    • More explicit and self-documenting than @Path("/*")
    • Works seamlessly in both server-side REST methods and client-side remote proxies
    • Supports all @Path features: default values, custom parsers/serializers, schema validation, OpenAPI documentation
    • Automatically configured with required=false and allowEmptyValue=true (path remainder is optional)

    Usage Example - Server Side:

    @RestGet("/files/*")
    public File getFile(@PathRemainder String path) {
    return new File(path);
    }

    Usage Example - Client Side:

    @Remote
    public interface FileService {
    @RemoteGet("/files/{+remainder}")
    File getFile(@PathRemainder String remainder);
    }

    Implementation Details:

    • Created PathRemainderArg resolver for server-side parameter resolution
    • Added support in RemoteOperationArg for client-side remote proxies
    • Extended HttpPartSchema.Builder with apply(PathRemainder) method
    • Comprehensive unit tests for server-side, including default values and mixed parameters

    Documentation:

    • Updated /docs/topics/09.03.04.PathVariables.md with path remainder examples
    • Updated /docs/topics/11.10.08.Path.md for remote proxy usage

CSV Serializer - Object Swap Support

  • Full Object Swap Support: The CsvSerializer now supports object swaps, bringing it to feature parity with other Juneau serializers like JSON, XML, and UON.

    note

    The CsvParser is not yet implemented. CSV parsing support will be added in a future release, at which point swap support will be included.

    Key Features:

    • Bean Property Swaps: Automatically applies swaps registered via .swaps() to bean property values
    • Map Value Swaps: Transforms map values using registered swaps before CSV serialization
    • Simple Value Swaps: Applies swaps to standalone values (e.g., List<Date> to formatted date strings)
    • @Swap Annotation Support: Honors @Swap annotations on bean fields for property-specific transformations
    • Null-Safe: Gracefully handles null values without applying swaps

    Example Usage:

    // Define a custom swap
    public static class DateSwap extends StringSwap<Date> {
    private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");

    @Override
    public String swap(BeanSession session, Date date) {
    return df.format(date);
    }

    @Override
    public Date unswap(BeanSession session, String str, ClassMeta<?> hint) throws ParseException {
    return df.parse(str);
    }
    }

    // Use the swap with CSV serializer
    List<User> users = Arrays.asList(
    new User("john", new Date()),
    new User("jane", new Date())
    );

    CsvSerializer serializer = CsvSerializer.create()
    .swaps(DateSwap.class)
    .build();

    String csv = serializer.serialize(users);
    // Output: date,name
    // 2025-10-14,john
    // 2025-10-14,jane

    Common Use Cases:

    • Date Formatting: Transform Date objects to formatted strings ("yyyy-MM-dd", "MM/dd/yyyy", etc.)
    • Enum Customization: Convert enums to custom string representations
    • Complex Object Flattening: Transform nested objects into simple string representations (e.g., Address"street|city|state")
    • Type Conversion: Convert numeric types to formatted strings (e.g., currency, percentages)

    Implementation Details:

    • Bean property values are automatically swapped via BeanPropertyMeta.get() which calls toSerializedForm()
    • Map values and simple values are explicitly swapped via the new applySwap() helper method
    • Swap exceptions are wrapped as RuntimeException to maintain compatibility with lambda expressions
    • The implementation follows the same swap resolution pattern as JsonSerializerSession
  • Comprehensive Test Coverage: Added 8 new unit tests covering:

    • Swaps on bean properties with serializer-level registration
    • Swaps on map values
    • Swaps on simple values (collections of swappable objects)
    • Null value handling with swaps
    • Custom object swaps for complex types
    • @Swap annotation on bean fields
    • Enum serialization

XML Serialization

  • Text Node Delimiter: Added textNodeDelimiter property to XmlSerializer and HtmlSerializer to control spacing between consecutive text nodes.

    Example usage:

    XmlSerializer serializer = XmlSerializer.create()
    .textNodeDelimiter(" ")
    .build();

    With this setting, serializing:

    new Audio().children("a", "b", new Strong("c"));

    Produces: <audio>a b<strong>c</strong></audio>
    Instead of: <audio>ab<strong>c</strong></audio>

    This improves readability of HTML5 bean output by adding proper spacing between text elements.

  • Disable JSON Type Tags: Added disableJsonTags() method to XmlSerializer and HtmlSerializer to disable JSON type identifier tags in XML output.

    Example usage:

    XmlSerializer serializer = XmlSerializer.create()
    .disableJsonTags()
    .build();

    When enabled, JSON type tags (e.g. <string>) and attributes will not be added to the output. This produces cleaner XML but may affect parsing accuracy as type information is lost.

    Note: Disabling JSON tags can cause different data to be parsed (e.g., strings instead of numbers) since type information is not preserved in the XML.

Bug Fixes

  • @Beanp Annotation Inheritance: Fixed a critical bug where @Beanp and @Name annotations were not properly inherited when bean property methods (getters, setters, extraKeys) were overridden in subclasses without re-annotating them. This caused the serialization framework to create duplicate bean properties with different names (one from the parent's annotation, one from the method name), resulting in SerializeException: ELEMENTS and ELEMENT properties found on the same bean. These cannot be mixed.

    The Problem:

    public class Parent {
    @Beanp("c")
    public List<String> getChildren() { return children; }
    public Parent setChildren(List<String> children) {
    this.children = children;
    return this;
    }
    }

    public class Child extends Parent {
    @Override
    public List<String> getChildren() { return super.getChildren(); }
    @Override
    public Child setChildren(List<String> children) { // Property name changed to "children"!
    super.setChildren(children);
    return this;
    }
    }

    Previously, the Child class would have TWO properties:

    • Property "c" from the parent getter's @Beanp annotation
    • Property "children" from the overridden setter (no annotation inherited)

    This caused serialization failures for classes like HTML5 elements where fluent setter overrides are common.

    The Fix:

    Two changes were made to properly inherit annotations:

    1. BeanMeta.inheritParentAnnotations() - Added a new method that walks up the class hierarchy when a method is overridden to find and inherit @Beanp and @Name annotations from parent methods. This ensures property names remain consistent across the inheritance chain.

    2. BeanPropertyMeta.getAllAnnotationsParentFirst() - Enhanced to walk up the method inheritance hierarchy for getter, setter, and extraKeys methods via a new forEachParentMethod() helper. This enables full annotation inheritance from parent methods across superclasses and interfaces.

    Impact:

    This fix allows developers to override fluent setters to change the return type without needing to re-annotate every method with @Beanp, @Xml, @Json, and other serialization annotations. The annotations are now automatically inherited from the parent method, ensuring consistent behavior.

    Example (Now Works Correctly):

    public class HtmlElementContainer {
    @Beanp("c")
    @Xml(format=XmlFormat.ELEMENTS)
    public List<Object> getChildren() { return children; }
    public HtmlElementContainer setChildren(List<Object> children) {
    this.children = children;
    return this;
    }
    }

    public class Figure extends HtmlElementContainer {
    @Override // ✓ No re-annotation needed - inherits @Beanp("c") and @Xml
    public Figure setChildren(List<Object> children) {
    super.setChildren(children);
    return this;
    }
    }
  • @Beanp(format) Not Working: Fixed a bug where the @Beanp(format="...") annotation was not applying String.format() patterns during bean serialization. The issue was caused by missing calls to getPropertySwap() when processing @Beanp annotations on fields and getter methods in BeanPropertyMeta.Builder. This feature now correctly formats numeric fields using Java format strings (e.g., @Beanp(format="%.2f") for floating-point values, @Beanp(format="%05d") for zero-padded integers, @Beanp(format="$%.2f") for currency formatting).

    Example:

    public class Product {
    @Beanp(format="$%.2f")
    public float price = 19.99f; // Serializes as "$19.99"

    @Beanp(format="%05d")
    public int id = 42; // Serializes as "00042"
    }
  • @HtmlDocConfig(navlinks) Query String Support: Fixed a limitation where @HtmlDocConfig(navlinks) required special protocols (context:, servlet:, request:) to include a slash before query strings or hash fragments. Now supports more natural syntax:

    • foo:request:?key=value (previously required foo:request:/?key=value)
    • foo:request:#section (previously required foo:request:/#section)
    • foo:context:?key=value (previously required foo:context:/?key=value)

    The UriResolver class now correctly handles these special protocols with or without a trailing slash, making it easier to construct navigation links with query parameters or hash fragments.

juneau-bct (NEW MODULE)

Bean-Centric Testing Framework

A new testing module that provides fluent, powerful APIs for testing Java beans with minimal boilerplate code.

Enhanced Stringifier Support

  • New charArrayStringifier: Added charArrayStringifier() to the Stringifiers class for simplified char array testing
    • Converts char arrays directly to strings using new String(chars)
    • Enables cleaner test assertions: assertString("Hello", charArray) instead of assertArrayEquals(new char[]{'H','e','l','l','o'}, charArray)
    • Automatically registered in defaultSettings() for immediate use
    • Comprehensive unit tests added covering various char array scenarios
    • Updated documentation with examples and built-in stringifier reference

Key Features:

  • Fluent Assertion API: Chain multiple assertions with readable, natural language syntax

    assertBean(myBean)
    .property("name").is("John")
    .property("age").isType(Integer.class).is(25)
    .property("email").matches(".*@example\\.com");
  • Deep Property Path Navigation: Test nested bean properties with dot notation and collection indexing

    assertBean(swagger)
    .asJson().is("paths.'/users'.get.summary", "Get all users")
    .asJson().is("definitions.User.properties.id.type", "integer");
  • Serialization Testing: Round-trip testing for JSON, XML, HTML, and other formats

    assertBean(bean)
    .asJson().is("{name:'John',age:25}")
    .asXml().contains("<name>John</name>");
  • Collection Assertions: Simplified testing of arrays, lists, and maps

    assertBean(swagger)
    .property("tags").asCollection().size().is(3)
    .property("tags[0].name").is("users");
  • Exception Testing: Clean assertions for exception testing

    assertThrown(() -> bean.setRequired(null))
    .isType(IllegalArgumentException.class)
    .message().contains("cannot be null");
  • Test Utilities: Helper methods for common testing patterns

    • assertMap() - Map-specific assertions
    • assertList() - List-specific assertions
    • assertString() - String pattern matching and validation
    • assertObject() - Generic object assertions

Benefits:

  • Reduces test code verbosity by up to 70%
  • Improves test readability and maintainability
  • Provides consistent testing patterns across the codebase
  • Supports all Juneau serialization formats
  • Works seamlessly with JUnit 5

Example Usage:

@Test
void testSwaggerDocument() {
Swagger swagger = new Swagger()
.info(new Info().title("My API").version("1.0"))
.path("/users", new PathItem()
.get(new Operation()
.summary("Get users")
.response("200", new Response().description("Success"))));

assertBean(swagger)
.asJson().is("info.title", "My API")
.asJson().is("info.version", "1.0")
.asJson().is("paths.'/users'.get.summary", "Get users")
.asJson().is("paths.'/users'.get.responses.'200'.description", "Success");
}

See the module documentation for complete API reference and examples.

juneau-shaded (NEW MODULE)

Shaded Artifacts for Simplified Dependency Management

Introduced a new juneau-shaded module that provides five shaded (uber) JAR artifacts. These artifacts bundle multiple Juneau modules into single JARs, making them ideal for:

  • Bazel and strict dependency build systems - Eliminates the need to manually declare all transitive Juneau dependencies
  • Simplified Maven/Gradle projects - One dependency instead of many
  • Standalone applications - Convenient uber JAR deployment
  • Quick prototyping - Get started faster with fewer dependency decisions

Available Shaded Artifacts

ArtifactSizeContents
juneau-shaded-core2.0 MBCore marshalling and configuration modules
juneau-shaded-rest-client3.8 MBCore + REST client functionality
juneau-shaded-rest-server3.8 MBCore + REST server functionality
juneau-shaded-rest-server-springboot3.8 MBREST server + Spring Boot integration
juneau-shaded-all4.0 MBComplete Juneau framework in one JAR

Maven Usage

<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-shaded-all</artifactId>
<version>${juneau.version}</version>
</dependency>

Bazel Usage

maven_jar(
name = "juneau_all",
artifact = "org.apache.juneau:juneau-shaded-all:${juneau.version}",
)

java_library(
name = "my_lib",
srcs = ["MyCode.java"],
deps = ["@juneau_all//jar"],
)

Key Features

  • Bundles all Juneau modules - No need to declare transitive Juneau dependencies
  • External dependencies still required - Apache HttpClient, Jakarta Servlet API, etc. must be declared separately
  • No code changes needed - All imports and APIs remain the same
  • Maven Shade Plugin - Uses industry-standard plugin for creating uber JARs
  • Service file merging - Automatically merges META-INF/services files
  • Dependency-reduced POMs - Shows exactly which external dependencies are needed

Included in Distribution

All five shaded artifacts are now included in the binary distribution under /shaded/:

  • juneau-shaded-core-${version}.jar
  • juneau-shaded-rest-client-${version}.jar
  • juneau-shaded-rest-server-${version}.jar
  • juneau-shaded-rest-server-springboot-${version}.jar
  • juneau-shaded-all-${version}.jar

Documentation

Deprecation of juneau-all

The previous juneau-all module has been deprecated and removed in favor of juneau-shaded-all. The shaded version provides the same functionality with better support for modern build systems like Bazel.

Migration:

  • Replace juneau-all with juneau-shaded-all in your dependencies
  • No code changes required - all imports remain the same

juneau-rest-client

RestRequest and RestResponse

  • Try-With-Resources Support: Enhanced RestRequest and RestResponse to work cleanly with try-with-resources blocks:

    • RestRequest now implements AutoCloseable and automatically closes RestResponse if a response has been created
    • close() methods no longer throw checked exceptions (following AutoCloseable best practices)
    • Unchecked exceptions are thrown for critical failures
    • Checked exceptions (including RestCallException) are logged but not thrown, preventing exceptions from being masked in try-with-resources

    Example usage:

    RestClient client = RestClient.create().build();
    try (RestRequest req = client.get("/api/users")) {
    String response = req.run().getContent().asString();
    } // Automatic cleanup with proper exception handling
  • Improved Error Handling: Added comprehensive logging for exceptions caught during cleanup to aid debugging while maintaining AutoCloseable compliance and preventing exception masking in try-catch blocks

Remote Proxy Default Values

  • Parameter-Level and Method-Level Default Values: Added support for specifying default values on remote proxy interface parameters and methods using the def attribute on HTTP part annotations:

    • @Header(name="...", def="...") - Default HTTP request headers
    • @Query(name="...", def="...") - Default query string parameters
    • @FormData(name="...", def="...") - Default form post parameters
    • @Path(name="...", def="...") - Default path variables
    • @Content(def="...") - Default request body (new attribute)
    • Parameter-level defaults take precedence over method-level defaults when both are specified

    Parameter-level defaults example:

    @Remote(path="/petstore")
    public interface PetStore {

    @RemoteGet("/pets")
    Pet[] getPets(
    @Header(name="Accept-Language", def="en-US") String language,
    @Query(name="limit", def="10") Integer limit
    );
    }

    PetStore store = client.getRemote(PetStore.class, "http://localhost:10000");

    // Uses default language="en-US" and limit=10
    Pet[] pets1 = store.getPets(null, null);

    // Uses custom language, default limit=10
    Pet[] pets2 = store.getPets("fr-FR", null);

    Method-level defaults example:

    @RemoteGet("/pets")
    @Header(name="Accept-Language", def="en-US")
    @Query(name="limit", def="10")
    Pet[] getPets(
    @Header("Accept-Language") String language,
    @Query("limit") Integer limit
    );

    Precedence example (parameter-level wins):

    @RemoteGet("/data")
    @Query(name="format", def="xml") // Method-level default
    String getData(
    @Query(name="format", def="json") String format // Takes precedence
    );
  • Multiple Defaults Support: Methods can have multiple default annotations of the same type, properly handling Java's @Repeatable annotation mechanism:

    @RemotePost("/resource")
    @Header(name="X-API-Key", def="default-key")
    @Header(name="X-Client-Version", def="1.0")
    String createResource(
    @Header("X-API-Key") String apiKey,
    @Header("X-Client-Version") String clientVersion
    );
  • Use Cases: Default values are particularly useful for:

    • API keys and authentication credentials
    • API versioning headers
    • Pagination limits and page sizes
    • Content negotiation (language, format)
    • Feature flags and debug modes
  • Behavior: Default values are only applied when the parameter value is null. Empty strings, zero values, and empty collections are considered valid values and will not trigger the default.

juneau-rest-server

CallLogger Exception Statistics Fix

  • Fixed CallLogger.getThrownStats(): Corrected the method to call thrownStore.add(Throwable) instead of thrownStore.getStats(Throwable)
    • The add() method properly increments the exception count and creates new stats if needed
    • The previous getStats() method only retrieved existing stats without updating counts
    • This ensures accurate exception statistics tracking in REST call logging
    • Exception counts and hashes are now properly maintained in the ThrownStore

Class-Level HTTP Parameter Defaults

  • @Rest Parameter Arrays: Added support for defining default HTTP parameter values at the REST class level that apply to all methods in the resource.

    • queryParams={...}: Define default query parameter values
    • headerParams={...}: Define default header parameter values
    • pathParams={...}: Define default path parameter values
    • formDataParams={...}: Define default form data parameter values

    Key features:

    • Eliminates duplication of common parameters across multiple methods
    • Class-level defaults are merged with method-level parameter annotations
    • Method-level values take precedence over class-level defaults
    • Affects validation, parsing, and OpenAPI/Swagger documentation generation

    Example usage:

    @Rest(
    queryParams={
    @Query(name="format", def="json", description="Output format (json|xml)"),
    @Query(name="verbose", def="false", schema=@Schema(type="boolean", description="Include verbose output"))
    },
    headerParams={
    @Header(name="X-API-Version", def="1.0", description="API version")
    },
    formDataParams={
    @FormData(name="action", def="submit", description="Form action")
    }
    )
    public class MyRestResource {

    @RestGet("/data")
    public Data getData(
    @Query("format") String format, // Inherits def="json" and description
    @Query("verbose") boolean verbose, // Inherits def="false" and schema
    @Header("X-API-Version") String version // Inherits def="1.0" and description
    ) {
    return formatData(data, format, verbose, version);
    }

    @RestPost("/update")
    public String updateData(
    @Query(name="format", def="xml") String format, // Overrides class-level def
    @Query("verbose") boolean verbose, // Still uses class-level def="false"
    @FormData("action") String action // Inherits def="submit" and description
    ) {
    return "Updated in " + format + " format with action: " + action;
    }
    }

    Benefits:

    • DRY Principle: Define common parameters once at the class level
    • Maintainability: Update defaults in one place instead of across multiple methods
    • Consistency: Ensures uniform parameter behavior across the REST API
    • Documentation: Class-level defaults are reflected in generated Swagger/OpenAPI specifications
    • Validation: Schema validation from class-level definitions applies to all methods

Documentation

Historical Javadocs

  • Javadocs Archive: Set up versioned javadocs hosting at /javadocs/ with:

    • Landing page with cards for all historical versions (7.1.0 through 9.1.0)
    • "Current (Development)" link pointing to /site/apidocs/ for latest Maven-generated docs
    • Helper scripts for adding new versions (add-javadocs.sh)
    • Comprehensive documentation for maintainers (README.md, JAVADOCS_SETUP.md)
  • Git LFS Considerations: Documented Git LFS limitations with Apache GitBox and alternative deployment strategies

Known Issues

None at this time.

Migration Notes

  • juneau-all → juneau-shaded-all: The juneau-all module has been removed. Update your dependencies:

    Before:

    <dependency>
    <groupId>org.apache.juneau</groupId>
    <artifactId>juneau-all</artifactId>
    <version>${juneau.version}</version>
    </dependency>

    After:

    <dependency>
    <groupId>org.apache.juneau</groupId>
    <artifactId>juneau-shaded-all</artifactId>
    <version>${juneau.version}</version>
    </dependency>

    No code changes are required - all imports and APIs remain the same.

    Alternatively, consider using a more specific shaded artifact if you don't need the full framework:

    • juneau-shaded-core - For marshalling/config only (2.0 MB)
    • juneau-shaded-rest-client - For REST client work (3.8 MB)
    • juneau-shaded-rest-server - For REST server work (3.8 MB)
  • @Schema Annotation: All existing code using @Schema annotations will continue to work without changes. The upgrade to Draft 2020-12 is fully backward compatible.

    Optional Migration (for new Draft 2020-12 features):

    • Replace exclusiveMaximum=true, maximum="100" with exclusiveMaximumValue="100" for cleaner syntax
    • Replace exclusiveMinimum=true, minimum="0" with exclusiveMinimumValue="0" for cleaner syntax
    • Use new properties like _const, examples, $comment, etc. as needed

    Example:

    // Old style (still works, but deprecated)
    @Schema(type="integer", exclusiveMaximum=true, maximum="100", exclusiveMinimum=true, minimum="0")

    // New style (recommended)
    @Schema(type="integer", exclusiveMaximumValue="100", exclusiveMinimumValue="0")
  • JsonSchema: Code using JsonSchema beans will continue to work without changes. For Draft 2020-12 features:

    • Use $id instead of id for new schemas
    • Use $defs instead of definitions for new schemas
    • Use exclusiveMaximumValue/exclusiveMinimumValue for numeric exclusive bounds (old boolean style still supported)
  • RestClient: If you have custom implementations that override close() methods, ensure they don't throw checked exceptions to comply with AutoCloseable contract.

  • License Headers: If you have custom tooling that depends on the old license header format, update it to recognize the new standard Apache License block comment format.

Discussion

Share feedback or follow-up questions for this page directly through GitHub.