Attempts
Fluent functional container for handling nullable values with validation pipelines
Attempts in BoxLang are an enhanced Java Optional that provides a fluent, functional approach to handling nullable values. Think of an Attempt as a smart container that can hold a value or be empty, with powerful methods for safely working with that value without null pointer exceptions.
Key Benefits: Attempts eliminate verbose null checks, enable declarative validation pipelines, and make your code more readable and maintainable through functional chaining patterns.
🌟 Why Use Attempts?
Attempts provide several advantages over traditional null handling:
✅ Eliminate null checks - No more
if (isNull(value))everywhere✅ Fluent API - Chain operations naturally:
.map().filter().orElse()✅ Built-in validation - Attach validation rules to create pipelines
✅ Immutable - Chain methods without mutating original values
✅ Functional programming - Write declarative, composable code
✅ Error handling - Convert missing values to exceptions or defaults gracefully
Traditional vs Attempt Approach
// ❌ Traditional approach - verbose and error-prone
user = userService.get( rc.id );
if ( isNull( user ) ) {
throw( "UserNotFoundException" );
}
email = user.getEmail();
if ( isNull( email ) ) {
throw( "Email not found" );
}
domain = email.listLast( "@" );
// ✅ Attempt approach - clean and declarative
attempt( userService.get( rc.id ) )
.flatMap( user -> user.getEmail() )
.map( email -> email.listLast( "@" ) )
.ifPresentOrElse(
domain -> println( "Domain: #domain#" ),
() -> throw( "User or email not found" )
);Attempts are also unmodifiable, so you can chain methods to handle the value more functionally, but it never mutates the original value. It can also be seeded with validation information to create validation pipelines.
💻 Attempts in Code
Let's see practical examples of Attempts in action:
🔄 Attempt States
An Attempt can exist in only two states:
State Checking Methods
isEmpty()
boolean
✅ Core check - true if no value present
isPresent()
boolean
✅ Core check - true if value exists
isNull()
boolean
🔍 Value check - true if value is null
hasFailed()
boolean
📝 Alias for isEmpty() - more readable for error handling
wasSuccessful()
boolean
📝 Alias for isPresent() - more readable for success cases
Rules for Present State
An attempt is present if and only if:
✅ The value is not null
An attempt is empty if:
❌ The value is null
❌ Created with
attempt()(no arguments)
Examples
Important: Only null makes an attempt empty. Empty strings, zero, false, and empty arrays/structs are all present values!
🏗️ Creating Attempts
Create attempts using the attempt() BIF (Built-In Function).
Empty Attempt
Create an empty attempt by calling attempt() with no arguments:
Attempt with Value
Pass any value or expression to create an attempt that may be present or empty:
Creation Patterns
🛠️ Working with Attempts
Once you have an attempt, you can interact with it using a rich set of fluent methods. These methods fall into several categories:
📖 Method Reference
🔍 State Checking Methods
isEmpty():boolean
isEmpty():booleanReturns true if the attempt has no value (is null).
isPresent():boolean
isPresent():booleanReturns true if the attempt contains a value (not null).
isNull():boolean
isNull():booleanReturns true if the value is null.
hasFailed():boolean / wasSuccessful():boolean
hasFailed():boolean / wasSuccessful():booleanFluent aliases for isEmpty() and isPresent() for more readable code.
equals( object ):boolean
equals( object ):booleanCompare two attempts for equality.
🎯 Value Retrieval Methods
get():any / getOrFail():any
get():any / getOrFail():anyGet the value of the attempt. Throws exception if the attempt is empty.
Warning: Only use get() when you're certain the value exists, or wrap in try/catch. Consider using orElse(), orElseGet(), or orThrow() for safer alternatives.
orElse( defaultValue ):any / getOrDefault( defaultValue ):any
orElse( defaultValue ):any / getOrDefault( defaultValue ):anyReturns the value if present, otherwise returns the provided default.
orElseGet( supplier ):any / getOrSupply( supplier ):any
orElseGet( supplier ):any / getOrSupply( supplier ):anyReturns the value if present, otherwise executes the supplier function and returns its result.
Performance Tip: Use orElseGet() with a lambda when the default value is expensive to compute. It's only called if the attempt is empty. Use orElse() for simple values that are cheap to create.
orThrow( [type], [message|exception] ):any
orThrow( [type], [message|exception] ):anyReturns the value if present, otherwise throws an exception.
🔄 Transformation Methods
map( mapper ):attempt
map( mapper ):attemptTransform the value if present by applying a function. Returns a new attempt with the transformed value.
filter( predicate ):attempt
filter( predicate ):attemptIf a value is present and matches the given predicate, returns an Attempt with the value; otherwise, returns an empty Attempt.
flatMap( mapper ):attempt
flatMap( mapper ):attemptApply a function that returns an Attempt, flattening the result to avoid nested Attempts. Use this when your mapping function itself returns an Attempt.
The Problem: If getEmail() returns an Attempt and you use map(), you get Attempt<Attempt<String>> (nested).
The Solution: flatMap() automatically unwraps one level, giving you Attempt<String>.
Complete Example: Before and After
Chaining Multiple FlatMaps
🎭 Conditional Action Methods
ifPresent( action ):attempt / ifSuccessful( action ):attempt
ifPresent( action ):attempt / ifSuccessful( action ):attemptExecute an action if a value is present. Returns the same attempt for chaining.
ifEmpty( action ):attempt / ifFailed( action ):attempt
ifEmpty( action ):attempt / ifFailed( action ):attemptExecute an action if the attempt is empty. Returns the same attempt for chaining.
ifPresentOrElse( presentAction, emptyAction ):attempt
ifPresentOrElse( presentAction, emptyAction ):attemptExecute one of two actions based on whether the value is present.
🔗 Chaining and Fallback Methods
or( supplier ):attempt
or( supplier ):attemptIf empty, returns a new Attempt produced by the supplier function. Great for fallback chains.
🌊 Stream Integration
stream():Stream<T>
stream():Stream<T>Convert the attempt to a Java Stream. If present, returns a single-element stream; if empty, returns an empty stream.
toOptional():Optional<T>
toOptional():Optional<T>Convert the attempt to a Java Optional for interoperability with Java code.
🔧 Utility Methods
toString():String
toString():StringGet a string representation of the attempt.
hashCode():int / equals( object ):boolean
hashCode():int / equals( object ):booleanStandard Java object methods for use in collections and comparisons.
✅ Validation Pipelines
Attempts provide powerful validation capabilities that let you attach validation rules and create validation pipelines. This is perfect for input validation, business rules, and data quality checks.
Validation Flow
Validation Process
Attach Matcher - Use
to{Method}()to register validation rulesCheck Validity - Use
isValid():booleanto test if value matches rulesConditional Actions - Use
ifValid()/ifInvalid()for conditional execution
Important: If the attempt is empty, isValid() always returns false, regardless of validation rules.
🎯 Validation Matchers
toBe( value ):attempt
toBe( value ):attemptValidate that the value exactly equals another value.
toBeBetween( min, max ):attempt
toBeBetween( min, max ):attemptValidate that a numeric value falls within a range (inclusive).
toBeType( type ):attempt
toBeType( type ):attemptValidate against BoxLang types using the isValid() BIF type system.
toMatchRegex( pattern, [caseSensitive=true] ):attempt
toMatchRegex( pattern, [caseSensitive=true] ):attemptValidate against a regular expression pattern.
toSatisfy( predicate ):attempt
toSatisfy( predicate ):attemptCustom validation using a closure/lambda. Most flexible approach.
🎨 Validation Method Reference
isValid()
boolean
✅ Returns true if value is present AND passes validation rules
ifValid( action )
attempt
🎯 Executes action if value is valid
ifInvalid( action )
attempt
❌ Executes action if value is invalid or empty
🔗 Validation Pipeline Examples
Multi-Stage Validation
API Input Validation
Form Validation Pipeline
💡 Validation Best Practices
✅ Attach validators early - Register validation rules as soon as you create the attempt
✅ Use specific validators - Prefer
toBeType()andtoMatchRegex()over generictoSatisfy()when possible✅ Chain validations - Combine multiple validators for complex rules
✅ Handle both paths - Use both
ifValid()andifInvalid()for complete flow control✅ Return attempts from validators - Enable fluent validation chains
✅ Document validation rules - Comment complex
toSatisfy()predicates✅ Test empty cases - Remember that empty attempts are always invalid
📚 Best Practices Summary
When to Use Attempts
✅ Good Use Cases:
External API calls - Network requests that may fail or return null
Database queries - Queries that may return no results
Configuration lookups - Config values that may not exist
File operations - File reads that may fail
User input processing - Form data that may be missing or invalid
Optional calculations - Operations that may not produce a result
Service responses - Backend services that may timeout or error
❌ Avoid Using Attempts For:
Simple null checks - Use elvis operator instead
Values that are never null - Unnecessary overhead
Control flow logic - Use conditionals instead
Exception handling - Use try/catch for exception control
Design Patterns
1. 🎯 Fail Fast with orThrow()
orThrow()2. 🔄 Transform Chains
3. 🎭 Conditional Logic
4. 🔗 Fallback Chains
5. ✅ Validation Pipelines
Performance Considerations
Immutability Cost - Each operation creates a new attempt; minimize in hot loops
Validation Overhead - Matchers add small overhead; cache validation results if needed
Closure Creation - Lambdas in
map/flatMaphave allocation cost; extract to functions in tight loopsStream Integration - Converting to streams adds overhead; only use when needed
Code Style Guidelines
Use descriptive variable names for attempts:
Prefer method chaining for readability:
Extract complex lambdas to separate functions:
Use
orElse()for simple defaults,orElseGet()for computed defaults:
Common Mistakes to Avoid
❌ Don't call get() without checking:
❌ Don't use map() when you need flatMap():
❌ Don't ignore validation rules:
❌ Don't create attempts for values you know exist:
🔗 Related Documentation
Conditionals - Control flow and logical operators
Exception Management - Error handling and try/catch
Null and Nothingness - Understanding null values in BoxLang
Closures - Lambda expressions and functional programming
Operators - Elvis operator and safe navigation
isValid() BIF - Type validation reference
🎓 Summary
The Attempt class is BoxLang's answer to safe optional value handling with built-in validation. It provides:
✅ Null Safety - Eliminates null pointer errors
✅ Validation - Built-in type and custom validation rules
✅ Fluent API - Chainable operations for clean code
✅ Functional Style - Map, filter, flatMap for transformations
✅ Explicit Handling - Forces you to think about empty cases
✅ Composability - Easily combine with other attempts
Use attempts whenever you're dealing with values that might not exist or might be invalid, and let the type system guide you to handle both cases correctly.
Pro Tip: Start using attempts for API calls, database queries, and configuration lookups. Once you're comfortable, expand usage to form validation and data transformation pipelines.
Last updated
Was this helpful?
