Annotations¶
JAWS provides annotations to control how proxy methods map to S3 operations. They can customize key resolution, traversal behavior, and result filtering.
@Name¶
Overrides the S3 key segment used when resolving a method to a child object.
By default the method name is used; @Name allows mapping to keys that
can't be expressed as Java identifiers.
public interface Module extends S3.Dir {
@Name("pom.xml")
PomFile pomXml();
@Name("src/main/java")
Sources sources();
}
Without @Name, pomXml() would resolve to the key segment pomXml.
With @Name("pom.xml"), it resolves to pom.xml.
@Parent¶
Navigates upward in the S3 key hierarchy. The annotated method must return
an interface extending S3. The value specifies how many levels to go up
(default is 1).
public interface Version extends S3.Dir {
// Go up one level: version → artifact
@Parent
ArtifactDir artifact();
// Go up two levels: version → artifact → group
@Parent(2)
GroupDir group();
// Go up three levels: version → artifact → group → repository root
@Parent(3)
Repository repository();
}
This is useful for navigating from a deeply nested proxy back to an ancestor without manually tracking the path.
Warning
A NoParentException is thrown at runtime if the proxy is already at the
bucket root and no further parent exists.
@Recursive¶
Marks a listing method as recursive — all descendants are returned, not just
immediate children. Without @Recursive, all listing methods use a
delimiter-based listing that returns only immediate children at one level.
public interface Repository extends S3.Dir {
// All descendant objects (flat listing, single request)
@Recursive
Stream<S3File> everything();
// All descendant directories (tree walk, one request per prefix)
@Recursive
Stream<S3.Dir> layout();
}
@Recursive is a marker annotation with no attributes. There is no depth
control; for depth-limited traversal, use the S3File.walk() methods directly.
See Listing & Recursion for a detailed walkthrough.
@Prefix¶
Narrows a listing to only S3 keys that begin with the given prefix.
The value is relative — it is appended to the current proxy's position in
the key hierarchy. The prefix is sent server-side in the ListObjects request,
so non-matching keys are never returned by AWS.
public interface Repository extends S3.Dir {
// Only list artifacts under org/apache
@Prefix("org/apache")
Stream<S3File> apacheArtifacts();
// Recursively list only keys under the org/ namespace
@Recursive
@Prefix("org/")
Stream<S3File> orgArtifacts();
}
Partial prefixes¶
The prefix doesn't need to end with a delimiter. It can be any key prefix, including a partial name:
public interface Tomcat extends S3.Dir {
// Only version directories starting with "10."
// Matches 10.1.18/, 10.1.19/, etc.
// Skips 9.0.85/, 11.0.0/, etc.
@Prefix("10.")
Stream<S3File> version10();
}
Because the prefix is applied server-side, a directory with hundreds of entries can be efficiently narrowed to just the relevant subset.
@Suffix¶
Filters results by file name ending. A shorthand for the common case of filtering by file extension. Multiple values are OR'd.
public interface Assets extends S3.Dir {
@Suffix(".css")
Stream<S3File> stylesheets();
@Suffix({".jpg", ".png", ".gif"})
Stream<S3File> images();
}
@Suffix is repeatable with an exclude flag. Use exclude = true to
remove entries matching a suffix:
public interface Assets extends S3.Dir {
// All .jar files except sources and javadoc jars
@Suffix(".jar")
@Suffix(value = {"-sources.jar", "-javadoc.jar"}, exclude = true)
Stream<S3File> binaryJars();
}
Type-level suffix¶
@Suffix can also be placed on an interface. It applies to all methods that
return that type — both listings and single-arg lookups:
@Suffix(".parquet")
public interface ParquetFile extends S3.File {
// Stream<ParquetFile> listings filter to .parquet files only
// ParquetFile partition(String name) validates name ends with .parquet
}
@Match¶
Filters results by a regular expression on the file name. The entire name
must match (implied ^ and $). Uses Pattern.asMatchPredicate().
public interface Reports extends S3.Dir {
@Match("daily-\\d{4}-\\d{2}-\\d{2}\\.csv")
Stream<S3File> dailyReports();
@Match(".*\\.(jpg|png)")
Stream<S3File> images();
}
@Match is repeatable with an exclude flag. Use exclude = true to
remove entries matching a pattern:
public interface Assets extends S3.Dir {
// All CSS files except reset.css
@Match(".*\\.css")
@Match(value = "reset\\.css", exclude = true)
Stream<S3File> cssExceptReset();
}
@Match can also be placed on an interface for type-level filtering and
validation. Listings return only matching entries, and single-arg methods
reject names that don't match.
@Filter¶
Applies an arbitrary client-side predicate to filter results. The annotation
takes a Predicate<S3File> class that must have a no-arg constructor.
public class IsJar implements Predicate<S3File> {
@Override
public boolean test(S3File file) {
return file.getName().endsWith(".jar");
}
}
public interface VersionDir extends S3.Dir {
@Filter(IsJar.class)
Stream<S3File> jars();
}
Type-level filters¶
@Filter can also be placed on an interface. It applies to all methods that
return that type — both listings and single-arg lookups:
@Filter(IsJar.class)
public interface JarFile extends S3.File {
// Stream<JarFile> listings filter to .jar files only
// JarFile artifact(String name) validates name passes IsJar
}
public interface VersionDir extends S3.Dir {
Stream<JarFile> artifacts(); // only .jar files
}
@Filter is repeatable — multiple filters are combined with AND logic.
Filter evaluation order¶
When multiple filter annotations are present, they are applied in order — simplest first, most complex last:
- @Prefix — server-side
- @Suffix includes —
String.endsWith() - @Suffix excludes
- @Match includes — compiled regex
- @Match excludes
- @Filter — arbitrary
Predicate<S3File>
Each filter only sees entries that passed the previous ones. Interface-level filters run before method-level filters within each category.
Tip
Use @Suffix for extension checks and @Match for name patterns.
Reserve @Filter for cases that need access to the full S3File.
Prefer @Prefix over all of these when possible — it filters
server-side and reduces the data transferred from AWS.
@Delimiter¶
Sets the delimiter for the ListObjects request. Can be used on both
immediate listing and @Recursive methods.
@Marker¶
Sets the starting position for a listing. Keys before the marker are skipped. This is the low-level pagination mechanism used by S3.
Note
In most cases, JAWS handles pagination automatically. @Marker is
useful when you need to start a listing from a known position.