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-shadedwith 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
defattribute 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
HtmlBuilderintegration - Standardized license headers across all Java files
- Enhanced test coverage for OpenAPI 3.0 and Swagger 2.0 beans
- Removed
finalmodifiers from many classes to improve testability with Mockito and other mocking frameworks - Improved
RestRequestandRestResponseto 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-allin favor ofjuneau-shaded-all
juneau-dto
juneau-bean-jsonschema
-
JSON Schema Draft 2020-12 Compliance: The
JsonSchemabean has been upgraded to support JSON Schema Draft 2020-12 while maintaining backward compatibility with Draft 04.New properties added:
$id(replacesid)$defs(replacesdefinitions)const- Single constant value validationexamples- Array of example valuesif,then,else- Conditional schema validationreadOnly,writeOnly- Property access controlcontentMediaType,contentEncoding- String content metadataprefixItems- Array prefix validationunevaluatedItems,unevaluatedProperties- Unevaluated content handlingdependentSchemas,dependentRequired- Property dependencies
Modified properties:
exclusiveMaximum/exclusiveMinimum- Now support numeric values (Draft 2020-12) in addition to boolean (Draft 04)enum- Changed toList<Object>to support any type
-
Improved Maintainability: Removed explicit
@Bean(properties)annotation in favor of automatic alphabetical property discovery withJsonSerializer.DEFAULT_SORTED. -
Enhanced Documentation:
- Updated package-info with comprehensive usage examples
- Added migration guide from Draft 04 to Draft 2020-12
- Merged
package2.htmlcontent intopackage-info.javafor consolidated documentation
-
Bug Fixes:
- Added
@BeanIgnoreto internal setters (setName,setSchemaMap) to prevent serialization issues in HTML round-trip tests - Fixed fallback logic for deprecated properties to prevent double serialization
- Added
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
- Added comprehensive tests for
-
Collection Method Consistency: Ensured all collection properties have the standard 4-method pattern:
setX(X...)- varargs settersetX(Collection<X>)- Collection setteraddX(X...)- varargs adderaddX(Collection<X>)- Collection adder
-
Bug Fixes:
- Fixed
OpenApi.resolveRefs()to properly handleserversarray references - Added null-safety checks in various
resolveRefs()implementations
- Fixed
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
- Added comprehensive tests for
-
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
- Fixed
juneau-bean-html5
-
@Html(style)Annotation: Addedstyle()property to the@Htmlannotation 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()andrender()are specified, the render takes precedence
- Provides a simpler alternative to
-
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
HtmlBuildercreator 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
HtmlBuildermethods 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:
Concurrent2KeyHashMap→ConcurrentHashMap2KeyConcurrent3KeyHashMap→ConcurrentHashMap3KeyConcurrent4KeyHashMap→ConcurrentHashMap4KeyConcurrent5KeyHashMap→ConcurrentHashMap5Key
ConcurrentHashMapXKey Refactoring:
The ConcurrentHashMapXKey classes have been simplified to be pure multi-key concurrent hash maps:
- Removed
disabledandsupplierparameters from constructors - Now extend
ConcurrentHashMap<TupleX<K1,K2,...>,V>directly - Added null key validation (throws
IllegalArgumentExceptionfor null keys) - Simplified
get()andput()methods to useTupleclasses directly
New Cache Classes:
Introduced dedicated caching classes that separate caching concerns from the base concurrent maps:
Cache<K,V>extendsConcurrentHashMap<K,V>- Single-key cacheCache2<K1,K2,V>extendsConcurrentHashMap2Key<K1,K2,V>- Two-key cacheCache3<K1,K2,K3,V>extendsConcurrentHashMap3Key<K1,K2,K3,V>- Three-key cacheCache4<K1,K2,K3,K4,V>extendsConcurrentHashMap4Key<K1,K2,K3,K4,V>- Four-key cacheCache5<K1,K2,K3,K4,K5,V>extendsConcurrentHashMap5Key<K1,K2,K3,K4,K5,V>- Five-key cache
Cache Features:
- Builder pattern with fluent API
disableCaching()- Bypass cache and always invoke suppliermaxSize(int)- Automatic eviction when size exceeds thresholdlogOnExit()- Log cache statistics on JVM shutdownsupplier(FunctionX<K1,...,V>)- Default supplier for cache missesget(K1,...)- Retrieve using default supplierget(K1,..., Supplier<V>)- Retrieve with override suppliergetCacheHits()- 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
ConcurrentXKeyHashMapwithdisabled/suppliershould migrate toCacheX - Code using
ConcurrentXKeyHashMapwithout caching features requires only class name updates - Null keys are no longer allowed (previously bypassed cache, now throws
IllegalArgumentException)
Breaking Changes:
ConcurrentXKeyHashMapconstructors no longer acceptdisabledorsupplierparameters- Passing null keys to
get()orput()now throwsIllegalArgumentExceptioninstead 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
.propertiesfiles 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()andStringUtils.toHex8()methods to prevent invalid input processing:toHex4(int)now throwsNumberFormatExceptionfor negative numberstoHex8(long)now throwsNumberFormatExceptionfor 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
StateEnumfor improved type safety:- Updated
StringUtils.replaceVars()method to useStateEnum.S1andStateEnum.S2 - Updated
Utils.parseKeyValuePairs()method to useStateEnum.S1andStateEnum.S2 - Updated
UonParserSession.parseObject()method to useStateEnum.S1throughStateEnum.S4 - Updated
UrlEncodingParserSessionmethods to useStateEnum.S1throughStateEnum.S4 - Added missing
StateEnumimport toUrlEncodingParserSession - All state machines now use consistent enum-based state management
- Updated
-
Jakarta XML Bind Dependency Elimination: Replaced
jakarta.xml.bind-apidependency with modern Java time APIs:- Updated
StringUtils.toIsoDate(Calendar)to useZonedDateTimeandDateTimeFormatter.ISO_LOCAL_DATE - Updated
StringUtils.toIsoDateTime(Calendar)to useZonedDateTimeandDateTimeFormatter.ISO_OFFSET_DATE_TIME - Removed
import jakarta.xml.bind.*;fromStringUtils.java - Added
import java.time.*;andimport 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
- Updated
-
Code Cleanup: Removed commented-out code blocks to improve code maintainability:
- Removed entire commented-out
getResponseBeanMeta()method fromRestContext.java - Removed commented-out
findClasses()method fromBeanMeta.java - Removed commented-out control flow block from
JettyMicroservice.java - Removed debug
if(false)condition fromParserReader.java - Removed commented-out method call from
HtmlParserSession.java - Removed commented-out test assertion from
ThirdPartyProxyResource.java - Removed commented-out
list2()method fromCollectionUtils.java - Preserved test method section headers as they serve as useful documentation
- Removed entire commented-out
Mockito Compatibility
- Removed
finalModifiers: Removedfinalmodifiers 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
@Schemaannotation 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 deprecatedexclusiveMaximumValue()/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 prefixesunevaluatedItems()- Additional validation for unevaluated array itemsunevaluatedProperties()- Additional validation for unevaluated object propertiesdependentSchemas()- Conditional subschemas based on property presencedependentRequired()- Conditionally required properties_if()/_then()/_else()- Conditional schema validation$defs()- Reusable schema definitions (replacesdefinitions)
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/exclusiveMinimumproperties still work but are deprecated - New numeric
exclusiveMaximumValue/exclusiveMinimumValuetake 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"
) - Old boolean
-
Enhanced Validation:
- Added
constvalidation inHttpPartSchema- values must exactly match the constant - Updated
exclusiveMaximum/exclusiveMinimumvalidation to support both boolean flags (Draft 04) and numeric values (Draft 2020-12) - Proper error messages for all new validation types
- Added
-
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
HttpPartFormatenum 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 PointerREGEX- 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.URIparser - 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 UUIDValidation Points:
- Format validation occurs in both
validateInput()(for incoming string values) andvalidateOutput()(for serialized objects) - Validation failures throw
SchemaValidationExceptionwith detailed error messages - Format validation integrates seamlessly with existing pattern, min/max length, and other validations
Jakarta Bean Validation Integration
-
Reflective Jakarta Validation Support:
HttpPartSchemanow automatically detects and processes Jakarta Bean Validation constraints without requiring a direct dependency onjakarta.validation-api:Supported Constraints:
@NotNull→required(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)@Email→format(EMAIL)with automatic email validation@Positive→minimum(0) + exclusiveMinimum(true)@PositiveOrZero→minimum(0)@Negative→maximum(0) + exclusiveMaximum(true)@NegativeOrZero→maximum(0)@NotEmpty→required(true) + minLength(1) + minItems(1)@NotBlank→required(true) + minLength(1) + pattern(".*\\S.*")@DecimalMin(value, inclusive)→minimum(value)with optionalexclusiveMinimum@DecimalMax(value, inclusive)→maximum(value)with optionalexclusiveMaximum
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-apias 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
@PathRemainderas 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
@Pathfeatures: default values, custom parsers/serializers, schema validation, OpenAPI documentation - Automatically configured with
required=falseandallowEmptyValue=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
PathRemainderArgresolver for server-side parameter resolution - Added support in
RemoteOperationArgfor client-side remote proxies - Extended
HttpPartSchema.Builderwithapply(PathRemainder)method - Comprehensive unit tests for server-side, including default values and mixed parameters
Documentation:
- Updated
/docs/topics/09.03.04.PathVariables.mdwith path remainder examples - Updated
/docs/topics/11.10.08.Path.mdfor remote proxy usage
- More explicit and self-documenting than
CSV Serializer - Object Swap Support
-
Full Object Swap Support: The
CsvSerializernow supports object swaps, bringing it to feature parity with other Juneau serializers like JSON, XML, and UON.noteThe
CsvParseris 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
@Swapannotations 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,janeCommon Use Cases:
- Date Formatting: Transform
Dateobjects 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 callstoSerializedForm() - Map values and simple values are explicitly swapped via the new
applySwap()helper method - Swap exceptions are wrapped as
RuntimeExceptionto maintain compatibility with lambda expressions - The implementation follows the same swap resolution pattern as
JsonSerializerSession
- Bean Property Swaps: Automatically applies swaps registered via
-
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
@Swapannotation on bean fields- Enum serialization
XML Serialization
-
Text Node Delimiter: Added
textNodeDelimiterproperty toXmlSerializerandHtmlSerializerto 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 toXmlSerializerandHtmlSerializerto 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
@Beanpand@Nameannotations 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 inSerializeException: 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
Childclass would have TWO properties:- Property
"c"from the parent getter's@Beanpannotation - 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:
-
BeanMeta.inheritParentAnnotations()- Added a new method that walks up the class hierarchy when a method is overridden to find and inherit@Beanpand@Nameannotations from parent methods. This ensures property names remain consistent across the inheritance chain. -
BeanPropertyMeta.getAllAnnotationsParentFirst()- Enhanced to walk up the method inheritance hierarchy for getter, setter, and extraKeys methods via a newforEachParentMethod()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;
}
} - Property
-
@Beanp(format) Not Working: Fixed a bug where the
@Beanp(format="...")annotation was not applyingString.format()patterns during bean serialization. The issue was caused by missing calls togetPropertySwap()when processing@Beanpannotations on fields and getter methods inBeanPropertyMeta.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 requiredfoo:request:/?key=value)foo:request:#section(previously requiredfoo:request:/#section)foo:context:?key=value(previously requiredfoo:context:/?key=value)
The
UriResolverclass 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 theStringifiersclass for simplified char array testing- Converts char arrays directly to strings using
new String(chars) - Enables cleaner test assertions:
assertString("Hello", charArray)instead ofassertArrayEquals(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
- Converts char arrays directly to strings using
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 assertionsassertList()- List-specific assertionsassertString()- String pattern matching and validationassertObject()- 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
| Artifact | Size | Contents |
|---|---|---|
| juneau-shaded-core | 2.0 MB | Core marshalling and configuration modules |
| juneau-shaded-rest-client | 3.8 MB | Core + REST client functionality |
| juneau-shaded-rest-server | 3.8 MB | Core + REST server functionality |
| juneau-shaded-rest-server-springboot | 3.8 MB | REST server + Spring Boot integration |
| juneau-shaded-all | 4.0 MB | Complete 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/servicesfiles - 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}.jarjuneau-shaded-rest-client-${version}.jarjuneau-shaded-rest-server-${version}.jarjuneau-shaded-rest-server-springboot-${version}.jarjuneau-shaded-all-${version}.jar
Documentation
- Shaded Artifacts Overview - Complete guide with Bazel examples
- juneau-shaded-core - Core functionality
- juneau-shaded-rest-client - REST client
- juneau-shaded-rest-server - REST server
- juneau-shaded-rest-server-springboot - Spring Boot
- juneau-shaded-all - Complete framework
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-allwithjuneau-shaded-allin your dependencies - No code changes required - all imports remain the same
juneau-rest-client
RestRequest and RestResponse
-
Try-With-Resources Support: Enhanced
RestRequestandRestResponseto work cleanly with try-with-resources blocks:RestRequestnow implementsAutoCloseableand automatically closesRestResponseif a response has been createdclose()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
defattribute 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
@Repeatableannotation 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 ofthrownStore.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
- The
Class-Level HTTP Parameter Defaults
-
@RestParameter 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 valuesheaderParams={...}: Define default header parameter valuespathParams={...}: Define default path parameter valuesformDataParams={...}: 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-allmodule 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
@Schemaannotations 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"withexclusiveMaximumValue="100"for cleaner syntax - Replace
exclusiveMinimum=true, minimum="0"withexclusiveMinimumValue="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") - Replace
-
JsonSchema: Code using
JsonSchemabeans will continue to work without changes. For Draft 2020-12 features:- Use
$idinstead ofidfor new schemas - Use
$defsinstead ofdefinitionsfor new schemas - Use
exclusiveMaximumValue/exclusiveMinimumValuefor numeric exclusive bounds (old boolean style still supported)
- Use
-
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.
Share feedback or follow-up questions for this page directly through GitHub.