Resolving Profiles
This guide explains how to resolve OSCAL profiles programmatically using liboscal-java.
OSCAL profiles are a powerful mechanism for customizing security control catalogs. Instead of copying and manually editing a catalog like NIST SP 800-53, organizations create profiles that:
- Select controls - Choose which controls apply to their environment
- Set parameters - Fill in organization-specific values for control parameters
- Modify content - Add guidance, alter statements, or customize controls
- Layer customizations - Import other profiles to build on existing baselines
However, many tools that consume OSCAL content expect a simple catalog, not a profile with references to external catalogs. Profile resolution is the process of “flattening” a profile into a self-contained catalog that includes all selected controls with all modifications applied.
For example, if you have a profile that imports NIST SP 800-53 and selects the FedRAMP High baseline controls, profile resolution produces a catalog containing just those controls, with any organization-specific modifications already applied.
Profile resolution takes an OSCAL profile and produces a resolved catalog containing:
- Only the selected controls (not the entire source catalog)
- All parameter values set to their resolved values
- All modifications (additions, alterations) applied
- A single, self-contained document with no external dependencies
The ProfileResolver accepts file paths, URLs, or document nodes and returns an IDocumentNodeItem containing the resolved catalog:
import dev.metaschema.oscal.lib.OscalBindingContext;
import dev.metaschema.oscal.lib.model.Catalog;
import dev.metaschema.oscal.lib.profile.resolver.ProfileResolver;
import dev.metaschema.core.model.IDocumentNodeItem;
import java.nio.file.Path;
// Resolve a profile directly from a file path
ProfileResolver resolver = new ProfileResolver();
IDocumentNodeItem resolvedDocument = resolver.resolve(Path.of("profile.json"));
// Extract the catalog from the resolved document
Catalog resolvedCatalog = (Catalog) resolvedDocument.getValue();
For profiles that import external resources, configure a document loader:
import dev.metaschema.databind.io.DefaultBoundLoader;
// Get the binding context
OscalBindingContext context = OscalBindingContext.instance();
// Create a document loader with the context
DefaultBoundLoader loader = new DefaultBoundLoader(context);
// Configure resolver with custom loader
ProfileResolver resolver = new ProfileResolver();
resolver.setDocumentLoader(loader);
// Resolve the profile
IDocumentNodeItem resolvedDocument = resolver.resolve(Path.of("profile.json"));
Catalog resolvedCatalog = (Catalog) resolvedDocument.getValue();
import dev.metaschema.databind.io.ISerializer;
// Write the resolved catalog
ISerializer<Catalog> serializer = context.newSerializer(
Format.JSON, Catalog.class);
serializer.serialize(resolvedCatalog, Path.of("resolved-catalog.json"));
import dev.metaschema.oscal.lib.OscalBindingContext;
import dev.metaschema.oscal.lib.model.Catalog;
import dev.metaschema.oscal.lib.model.Profile;
import dev.metaschema.oscal.lib.profile.resolver.ProfileResolver;
import dev.metaschema.databind.io.Format;
import dev.metaschema.databind.io.IDeserializer;
import dev.metaschema.databind.io.ISerializer;
import java.nio.file.Path;
public class ProfileResolutionExample {
public static void main(String[] args) throws Exception {
// Get binding context
OscalBindingContext context = OscalBindingContext.instance();
// Load profile
IDeserializer<Profile> profileReader = context.newDeserializer(
Format.JSON, Profile.class);
Profile profile = profileReader.deserialize(
Path.of("fedramp-high-profile.json"));
// Resolve
ProfileResolver resolver = new ProfileResolver();
Catalog resolved = resolver.resolve(profile);
// Save result
ISerializer<Catalog> catalogWriter = context.newSerializer(
Format.JSON, Catalog.class);
catalogWriter.serialize(resolved,
Path.of("fedramp-high-resolved.json"));
System.out.println("Resolved " +
resolved.getGroups().stream()
.flatMap(g -> g.getControls().stream())
.count() + " controls");
}
}
Understanding what happens during resolution:
Profile
│
├── 1. Load imported catalogs/profiles (recursive)
│
├── 2. Select controls (include/exclude)
│
├── 3. Apply modifications
│ ├── Set parameters
│ ├── Add content
│ └── Alter existing content
│
├── 4. Merge controls (from multiple imports)
│
└── 5. Generate resolved catalog
When profiles import remote catalogs:
import java.net.URI;
// Profile imports: "href": "https://example.com/catalog.json"
// The resolver will fetch remote resources automatically
ProfileResolver resolver = new ProfileResolver();
Catalog resolved = resolver.resolve(profile);
Profiles can import other profiles (chaining):
Base Catalog
↓
Profile A (selects controls)
↓
Profile B (adds customizations)
↓
Profile C (organization-specific)
↓
Resolved Catalog
Resolution handles chains automatically:
// Even if profile imports another profile, resolution is handled
ProfileResolver resolver = new ProfileResolver();
Catalog resolved = resolver.resolve(organizationProfile);
import dev.metaschema.oscal.lib.profile.resolver.ProfileResolutionException;
try {
Catalog resolved = resolver.resolve(profile);
} catch (ProfileResolutionException e) {
System.err.println("Resolution failed: " + e.getMessage());
// Handle specific resolution errors
} catch (IOException e) {
System.err.println("Failed to load import: " + e.getMessage());
// Handle I/O errors
}
After resolution, work with the catalog:
Catalog resolved = resolver.resolve(profile);
// Iterate controls
resolved.getGroups().forEach(group -> {
System.out.println("Group: " + group.getTitle());
group.getControls().forEach(control -> {
System.out.println(" Control: " + control.getId() +
" - " + control.getTitle());
});
});
// Find specific control
resolved.getGroups().stream()
.flatMap(g -> g.getControls().stream())
.filter(c -> c.getId().equals("ac-1"))
.findFirst()
.ifPresent(control -> {
System.out.println("Found: " + control.getTitle());
});
The resolver supports various options through the profile structure:
| Profile Element | Effect on Resolution |
|---|---|
import/include-all |
Include all controls from source |
import/include-controls |
Include specific controls |
import/exclude-controls |
Exclude specific controls |
merge/combine |
How to combine duplicate controls |
merge/flat |
Flatten control hierarchy |
modify/set-parameters |
Set parameter values |
modify/alters |
Modify control content |
- Cache resolved catalogs - Resolution can be expensive
- Handle remote failures - Network requests may fail
- Validate after resolution - Ensure result is valid
- Check for circular imports - Can cause infinite loops
Continue learning about liboscal-java with these related guides:
- Reading & Writing Data - Save resolved catalogs
- Executing Metapath - Query resolved content
- Validating with Constraints - Validate results

