DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
This document captures the discussion about validating <sources> configuration in Maven 4.x.
Scope
This document focuses exclusively on the Maven 4.x core implementation (master branch of apache/maven). Specifically:
- The
<sources>element introduced in POM model version 4.1.0 - The source/resource handling logic in
DefaultProjectBuilder.java - Validation gaps in the core that should be addressed
Compiler Plugin Compensation
Some validation gaps in the core are currently compensated by the maven-compiler-plugin, which performs its own validation at compile time, e.g.,
Mixed modular/non-modular sources → compiler plugin fails with "Mix of modular and non-modular sources", cf. ToolExecutor.java#L595-L608
However, relying on plugin-level validation is suboptimal:
- Later feedback: Errors appear during compilation, not during POM processing
- No line numbers: Plugin errors cannot reference the exact POM location
- Plugin-specific: Other language plugins (Kotlin, Groovy, Scala) may not have equivalent validation
- Incomplete coverage: Some invalid configurations are not caught at all (e.g., test-only modular projects)
The goal should be to implement proper validation in the Maven core, providing early, consistent feedback with precise error locations via ModelProblem.
Origin of This Discussion
This discussion was triggered by investigating how to best implement resource handling in the new modular source directory hierarchy. Maven 4.x introduces a unified <sources> element that supports modular layouts like:
src/
├── org.foo.moduleA/
│ ├── main/
│ │ ├── java/
│ │ └── resources/ ← Module-specific resources
│ └── test/
│ ├── java/
│ └── resources/
└── org.foo.moduleB/
├── main/
│ ├── java/
│ └── resources/ ← Module-specific resources
└── test/
However, the current implementation does not automatically pick up resources from the modular paths (src/<module>/main/resources). Instead, it always uses the legacy <resources> element which defaults to src/main/resources.
Solution: See Phase 1 for the implementation that automatically injects module-aware resources.
Current Workaround: Users can configure the maven-resources-plugin with explicit executions for each module:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources-moduleA</id>
<phase>process-resources</phase>
<goals><goal>copy-resources</goal></goals>
<configuration>
<resources>
<resource>
<directory>src/org.foo.moduleA/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-resources-moduleB</id>
<phase>process-resources</phase>
<goals><goal>copy-resources</goal></goals>
<configuration>
<resources>
<resource>
<directory>src/org.foo.moduleB/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
This workaround is verbose and error-prone. The investigation into implementing this properly in the core led to the Phase 1 implementation and revealed the broader set of validation and configuration handling issues documented here.
Problem Statement
Let's investigate some of the problems with the current implementation of <sources> handling in Maven 4.x. Maven 4.x introduces a new <sources> element that supports modular project layouts (src/<module>/<scope>/<lang>). However, the current implementation has several issues when handling the interaction between the new <sources> element and legacy configuration elements (<sourceDirectory>, <resources>, etc.).
This is not a comprehensive list, but highlights key problems that should be addressed in the core. A systematic Analysis and Proposed Solutions follow in subsequent sections.
Problem 1: Mixed Modular/Non-Modular Sources Accepted Silently
Mixing modular and non-modular sources in <sources> is an invalid configuration that will fail at compile time, but DefaultProjectBuilder accepts it silently without early validation.
Example - Mixed configuration:
<build>
<sources>
<source>
<scope>main</scope>
<lang>java</lang>
<module>org.foo.moduleA</module> <!-- Modular -->
</source>
<source>
<scope>main</scope>
<lang>java</lang>
<!-- No module - classic style -->
</source>
</sources>
</build>
| Expected Behavior | Actual Behavior |
|---|---|
| Early warning/error about inconsistent configuration | Silent acceptance by DefaultProjectBuilder |
| Clear feedback during project build | Delayed failure at compile time |
Current Workaround: The maven-compiler-plugin validates this at compile time and fails with:
"Mix of modular and non-modular sources."
See above (Compiler Plugin Compensation) for drawbacks of this approach.
Problem 2: Test-Only Modular Projects Get Classic Main Sources Injected
When a project configures only test sources in <sources> (no main sources), DefaultProjectBuilder silently injects the classic src/main/java as a main source directory.
Example - Integration test module with only test sources:
<build>
<sources>
<source>
<scope>test</scope>
<lang>java</lang>
<module>org.foo.integrationTests</module>
</source>
</sources>
</build>
| Expected Behavior | Actual Behavior |
|---|---|
| No main sources (project is test-only) | src/main/java silently added as main source |
Test sources from src/org.foo.integrationTests/test/java | Test sources work correctly |
Root cause: The hasMain boolean remains false when no <source> has scope=main + lang=java. Line 697-698 then adds build.getSourceDirectory() (defaults to src/main/java from Super POM).
See: DefaultProjectBuilder.java#L697-L698
Impact:
- If
src/main/javadoesn't exist → probably harmless (empty directory) - If
src/main/javaexists with old/unrelated code → it gets compiled! - Modular test project silently becomes a mixed modular-test + classic-main project
Use cases affected:
- Integration test modules (only contain tests)
- Test fixtures modules (shared test utilities)
- BOM/parent projects with test verification but no main code
Note: Unlike Problem 1, this is not caught by the compiler plugin - it silently succeeds but with potentially wrong behavior.
Problem 3: Resources Are Not Module-Aware
When a project uses modular sources, resources should follow the same modular layout. Currently, they don't.
Example - Modular Java sources without explicit resources:
<build>
<sources>
<source>
<scope>main</scope>
<lang>java</lang>
<module>org.foo.moduleA</module>
</source>
<source>
<scope>main</scope>
<lang>java</lang>
<module>org.foo.moduleB</module>
</source>
</sources>
</build>
| Expected Behavior | Actual Behavior |
|---|---|
Resources from src/org.foo.moduleA/main/resources | Resources from src/main/resources only |
Resources from src/org.foo.moduleB/main/resources | (legacy path, not module-aware) |
Impact: Module-specific resources (like module-specific configs) cannot be organized per-module.
Solution: See Phase 1 for the implementation.
Workaround (no longer needed): See Origin of This Discussion for how to configure the maven-resources-plugin with explicit executions for each module.
Problem 4: Legacy Configuration Silently Ignored
When <sources> is present, legacy elements like <sourceDirectory> are silently ignored without warning.
Example - Explicit sourceDirectory with sources:
<build>
<sourceDirectory>src/custom/java</sourceDirectory> <!-- User expects this to be used -->
<sources>
<source>
<scope>main</scope>
<lang>java</lang>
<module>org.foo.bar</module>
</source>
</sources>
</build>
| Expected Behavior | Actual Behavior |
|---|---|
| Warning: "sourceDirectory is ignored because sources are configured" | Silent - user doesn't know their config is ignored |
| Clear feedback to user | Confusion when src/custom/java isn't used |
Analysis
Core Design Question
How should the source reading and validation logic work?
The fundamental question is about priority and conflict resolution when both new (<sources>) and legacy (<sourceDirectory>, <resources>) configuration elements are present.
Key Design Principle: Modular Sources Have Priority
When a project uses the new <sources> element with modular configuration (<module> element), the modular approach should take precedence:
- Modular sources (
<sources>with<module>) demand proper modular layout and behavior - Non-modular sources (
<sources>without<module>) should not be used (error or warning) in a modular project - Legacy configuration (
<sourceDirectory>,<resources>) is used only as fallback when no<sources>are configured
This principle ensures:
- Clear, predictable behavior
- No silent mixing of old and new approaches
- Users explicitly opt-in to the new model
Path Resolution Logic
From DefaultSourceRoot.fromModel() — abbreviated pseudo code of the current implementation:
if (module specified) {
// Modular mode
path = directory specified ? baseDir.resolve(directory)
: baseDir/src/<module>/<scope>/<lang>
} else {
// Classic mode
path = directory specified ? baseDir.resolve(directory)
: baseDir/src/<scope>/<lang>
}
| Module | Directory | Mode | Result |
|---|---|---|---|
| No | No | Classic | src/<scope>/<lang> (default) |
| No | Yes | Classic | <directory> (override) |
| Yes | No | Modular | src/<module>/<scope>/<lang> (default) |
| Yes | Yes | Modular | <directory> (override, module = metadata) |
Strict vs Lenient Approach
Two approaches are possible for handling configuration conflicts:
Approach A: Strict (Fail Fast)
Configuration conflicts result in errors that fail the build.
| Rule | Condition | Severity | Message |
|---|---|---|---|
| R1 | <sources> present AND explicit <sourceDirectory> | ERROR | "Cannot combine <sources> with explicit <sourceDirectory>. Use one or the other." |
| R2 | <sources> with mixed module/no-module elements | ERROR | "Cannot mix modular and non-modular sources. All <source> elements must either have a module or none." |
| R3 | Duplicate <source> (same scope/lang/module) | ERROR | "Duplicate source definition for scope='X', lang='Y', module='Z'" |
| R4 | <sources> configured but classic directory exists on filesystem | WARN | "Directory 'src/main/java' exists but will be ignored because <sources> is configured" |
Approach B: Lenient (Warn and Continue)
Configuration conflicts result in warnings but the build continues.
| Rule | Condition | Severity | Message |
|---|---|---|---|
| R1' | <sources> present AND explicit <sourceDirectory> | WARN | "Explicit <sourceDirectory> will be ignored because <sources> is configured." |
| R2' | <sources> with mixed module/no-module elements | WARN | "Mixing modular and non-modular sources may lead to unexpected behavior." |
| R3 | Duplicate <source> (same scope/lang/module) | ERROR | "Duplicate source definition for scope='X', lang='Y', module='Z'" |
| R4 | <sources> configured but classic directory exists on filesystem | WARN | "Directory 'src/main/java' exists but will be ignored because <sources> is configured" |
Comparison
| Aspect | Strict (A) | Lenient (B) |
|---|---|---|
<sources> + explicit SD/TSD | ERROR | WARN |
| Mixed module/no-module | ERROR | WARN |
| Duplicate sources | ERROR | ERROR |
| Filesystem mismatch | WARN | WARN |
| Migration friendliness | Lower | Higher |
| User confusion risk | Lower | Higher |
| Fail-fast principle | Yes | No |
Recommendation
Approach A (Strict) is recommended for new projects because:
- Configuration conflicts indicate user error/misunderstanding
- Silent ignoring of explicit configuration is confusing
- Better to fail early than have unexpected behavior at compile time
- Users can easily fix by removing the conflicting element
Approach B (Lenient) could be considered if:
- Migration from Maven 3.x to 4.x needs to be smoother
- Tooling (IDEs) may generate both elements during transition
- A deprecation period is desired before enforcing strict rules
Unified Permutation Matrix
This matrix shows all configuration combinations and their expected behavior under each approach.
Legend
| Abbreviation | Meaning |
|---|---|
| SD | <sourceDirectory> - classic Maven 3.x element for main Java sources |
| TSD | <testSourceDirectory> - classic Maven 3.x element for test Java sources |
| S | <source> element within <sources> - Maven 4.x way to define sources |
| R | <resources> / <resource> - classic Maven 3.x element for resources |
| M | <module> element within <source> - specifies JPMS module name |
Java Sources
| # | Configuration | Current | Lenient | Strict | Compiler Plugin |
|---|---|---|---|---|---|
No <sources> (classic mode) | |||||
| 1 | SD=implicit (Super POM) | src/main/java | OK | OK | - |
| 2 | SD=explicit | user's path | OK | OK | - |
<sources> present (new mode) | |||||
| 3 | S(no M), SD=implicit | src/main/java | OK | OK | - |
| 4 | S(no M), SD=explicit | Silent ignore | WARN | ERROR | Not caught |
| 5 | S(M=X), SD=implicit | src/X/main/java | OK | OK | - |
| 6 | S(M=X), SD=explicit | Silent ignore | WARN | ERROR | Not caught |
| 7 | S(M=X) + S(M=Y) | Both paths | OK | OK | - |
| 8 | S(M=X) + S(no M) | Silent accept | WARN | ERROR | Caught ✓ |
| 9 | S(dir=custom, no M) | custom path | OK | OK | - |
| 10 | S(dir=custom, M=X) | custom (M=meta) | OK | OK | - |
| 11 | Duplicate S | Silent accept | ERROR | ERROR | Not caught |
| 12 | S(M=X) + src/main/java exists | Silent ignore | WARN | WARN | Not caught |
| 13 | S(scope=test, M=X) only | Injects src/main/java | No inject | No inject | Not caught ⚠️ |
Resources
| # | S Config | R Config | Current | Proposed (Phase 1) | Lenient | Strict |
|---|---|---|---|---|---|---|
| 1 | S(lang=resources) | R=any | Duplicates! | Use S, WARN if R explicit | Use S, WARN | Use S, WARN |
| 2 | S(java, no M) | R=implicit | src/main/resources | src/main/resources | OK | OK |
| 3 | S(java, no M) | R=explicit | User's path | User's path | OK | OK |
| 4 | S(java, M=X) | R=implicit | src/main/resources | Inject modular | Inject modular | Inject modular |
| 5 | S(java, M=X) | R=explicit | User's path | Inject modular, WARN | WARN | ERROR |
Current Implementation Status
As of commit 25c80d8
Implementation Location
The source/resource handling logic is in DefaultProjectBuilder.initProject():
// Lines 669-686: Iterate over <sources> and track what's present
boolean hasScript = false;
boolean hasMain = false; // true if ANY <source> has lang=java, scope=main
boolean hasTest = false; // true if ANY <source> has lang=java, scope=test
for (var source : sources) {
var src = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source);
project.addSourceRoot(src);
// ... tracking logic
}
// Lines 694-701: Decide whether to use legacy sourceDirectory/testSourceDirectory
// (Lines 687-692 contain a comment explaining this behavior)
if (!hasScript) {
project.addScriptSourceRoot(build.getScriptSourceDirectory());
}
if (!hasMain) {
project.addCompileSourceRoot(build.getSourceDirectory()); // Silent fallback
}
if (!hasTest) {
project.addTestCompileSourceRoot(build.getTestSourceDirectory()); // Silent fallback
}
// Lines 703-708: Resources are ALWAYS added from legacy elements (no checks!)
for (Resource resource : project.getBuild().getDelegate().getResources()) {
project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.MAIN, resource));
}
for (Resource resource : project.getBuild().getDelegate().getTestResources()) {
project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.TEST, resource));
}
Gap Analysis
| Issue | Description | Core Status | Compiler Plugin |
|---|---|---|---|
| No warnings | Explicit <sourceDirectory> silently ignored when <sources> present | Not implemented | Not caught |
| No mixed validation | Mixing S(M=X) with S(no M) silently accepted | Not implemented | Caught ✓ |
| Test-only injection | src/main/java injected for test-only modular projects | Not implemented | Not caught ⚠️ |
| No duplicates check | Same source can be defined multiple times | Not implemented | Not caught |
| No modular resources | Resources always from legacy <resources> element | ✅ Implemented (Phase 1) | N/A |
Key insight: Only the mixed modular/non-modular validation (#8) is currently caught by the compiler plugin. All other issues require core implementation.
What Works
- Processing
<source>elements and adding them as source roots - Fallback to legacy
<sourceDirectory>when no Java sources in<sources> - Path resolution for both modular and classic layouts
What's Missing
- Warnings when explicit configuration is ignored
- Validation for mixed modular/non-modular sources (currently deferred to compiler plugin)
- Correct handling of test-only modular projects (no main injection)
- Duplicate detection
Modular resource handling✅ Implemented in Phase 1
Implementation Approaches
Phase 1: Module-Aware Resource Handling (Implemented)
Goal: Enable modular resource handling without requiring explicit maven-resources-plugin configuration.
Status: ✅ Implemented in PR 11505
Implementation:
Resource tracking via
<sources>: (Lines 672-673)boolean hasMainResources = false; boolean hasTestResources = false; for (var source : sources) { if (Language.RESOURCES.equals(language)) { if (ProjectScope.MAIN.equals(scope)) { hasMainResources = true; } else { hasTestResources |= ProjectScope.TEST.equals(scope); } } }Module extraction to detect modular projects: (Lines 713-714, extractModules() at L1256)
Set<String> modules = extractModules(sources); boolean isModularProject = !modules.isEmpty();
Module-aware resource injection for modular projects: (Lines 729-786, createModularResourceRoot() at L1275)
- If resources configured via
<sources>: use them (already added during iteration) - If no resources in
<sources>: inject module-aware defaults for each module - Warn (as
ModelProblem) if legacy<resources>is present but ignored
if (isModularProject) { if (hasMainResources) { // Already added via <sources>, warn if legacy <resources> present } else { // Inject module-aware defaults: src/<module>/main/resources for (String module : modules) { project.addSourceRoot(createModularResourceRoot(baseDir, module, ProjectScope.MAIN, ...)); } } }- If resources configured via
Super POM default detection: (hasOnlySuperPomDefaults() at L1303)
hasOnlySuperPomDefaults()checks if<resources>contains only inherited defaults- Warnings are only issued for explicitly configured legacy resources, not Super POM defaults
Priority Hierarchy (Proposed):
| Priority | Condition | Behavior |
|---|---|---|
| 1 | Modular project + resources in <sources> | Use <sources> resources, warn if legacy present |
| 2 | Modular project + no resources in <sources> | Inject src/<module>/<scope>/resources for each module |
| 3 | Classic project + resources in <sources> | Use <sources> resources, warn if legacy present |
| 4 | Classic project + no resources in <sources> | Use legacy <resources> element |
Example - Would Work Without Explicit Plugin Configuration:
<build>
<sources>
<source>
<scope>main</scope>
<lang>java</lang>
<module>org.foo.moduleA</module>
</source>
<source>
<scope>main</scope>
<lang>java</lang>
<module>org.foo.moduleB</module>
</source>
</sources>
</build>
Resources would be automatically picked up from:
src/org.foo.moduleA/main/resourcessrc/org.foo.moduleB/main/resources
What's NOT Addressed in This Proposal:
- Problem 1: Mixed modular/non-modular sources validation (still deferred to compiler plugin)
- Problem 2: Test-only modular project main injection
- Problem 4: Warning when explicit
<sourceDirectory>is ignored - Duplicate source detection
- Per-module tracking (project-level booleans still used)
Phase 2: Comprehensive Refactoring
Goal: Full implementation of the design principles from Section 2 with proper validation.
Scope:
Per-module tracking instead of project-level booleans:
record ModuleConfig(boolean hasMain, boolean hasTest, boolean hasMainResources, boolean hasTestResources) {} Map<String, ModuleConfig> moduleConfigs = new HashMap<>();Validation in ModelValidator (not DefaultProjectBuilder):
- Move conflict detection to
validateEffectiveModel() - Report
ModelProblemwith line numbers and severity - Early feedback during POM processing
- Move conflict detection to
Strict validation rules (from Section 2.2):
Rule Condition Severity Message R1 <sources>+ explicit<sourceDirectory>ERROR "Cannot combine <sources>with explicit<sourceDirectory>"R2 Mixed modular/non-modular sources ERROR "Cannot mix modular and non-modular sources" R3 Duplicate <source>definitionERROR "Duplicate source for scope='X', lang='Y', module='Z'" R4 <sources>configured but classic dir existsWARN "Directory 'src/main/java' exists but will be ignored" Fix test-only modular project injection (Problem 2):
// Don't inject legacy main sources for modular projects if (!hasMain && !isModularProject) { project.addCompileSourceRoot(build.getSourceDirectory()); }Warning when explicit configuration is ignored (Problem 4):
if (hasMain && isExplicitSourceDirectory(build)) { // Report ModelProblem with line number }InputLocation-based detection for explicit vs inherited configuration:
InputLocation location = build.getLocation("sourceDirectory"); boolean isExplicit = location != null && !isSuperPomLocation(location);
Implementation Location:
| Component | Responsibility |
|---|---|
DefaultModelValidator | Validation rules R1-R4, early feedback with line numbers |
DefaultProjectBuilder | Source root creation, fallback logic |
DefaultSourceRoot | Path resolution (already implemented) |
Implementation Timeline
| Phase | Scope | Status |
|---|---|---|
| Phase 1 | Module-aware resource handling | ✅ Implemented (PR 11505) |
| Phase 2 | Full validation + per-module tracking + ModelValidator integration | Pending |
Reference Material
Model Version vs Maven Version
| Model Version | Maven Version | Notes |
|---|---|---|
| 4.0.0 | Maven 3.x | Classic POM model, no <sources> element |
| 4.1.0 | Maven 4.x | Introduces <sources> element |
| 4.2.0 | Future | Reserved for future use |
The <sources> element requires model version 4.1.0:
<project xmlns="http://maven.apache.org/POM/4.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 https://maven.apache.org/xsd/maven-4.1.0.xsd">
<modelVersion>4.1.0</modelVersion>
...
</project>
The Maven 4.x Unified Source Model
In Maven 4.x, all source types can be configured via <sources>:
<sources>
<!-- Java sources -->
<source>
<scope>main</scope>
<lang>java</lang>
<module>org.foo.bar</module>
</source>
<!-- Resources -->
<source>
<scope>main</scope>
<lang>resources</lang>
<module>org.foo.bar</module>
</source>
</sources>
The <lang> element accepts:
java- compiled by compiler plugin (default if omitted)resources- processed by resources pluginscript- deprecated, useresourcesinstead
Extensible via LanguageProvider SPI for other languages (Kotlin, Groovy, etc.).
Processing Flow
┌─────────────────────────────────────────────────────────────────┐
│ POM Model │
│ <sources> │
│ <source><lang>java</lang><module>X</module></source> │
│ <source><lang>resources</lang><module>X</module></source> │
│ </sources> │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DefaultProjectBuilder │
│ Creates SourceRoot objects from <source> elements │
│ Adds to project.addSourceRoot(...) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ MavenProject │
│ getEnabledSourceRoots(ProjectScope scope, Language language) │
│ - Plugins query this to find relevant sources │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ Compiler Plugin │ │ Resources Plugin │
│ Queries: lang=java │ │ Queries: lang=resources │
└─────────────────────────┘ └─────────────────────────┘
Detecting Explicit vs Implicit Configuration
To detect whether <sourceDirectory> was explicitly configured (vs inherited from Super POM):
// Option 1: Compare to known Super POM default
String superPomDefault = "${project.basedir}/src/main/java";
String resolvedDefault = baseDir.resolve("src/main/java").toString();
boolean isExplicit = !build.getSourceDirectory().equals(resolvedDefault);
// Option 2: Use InputLocation tracking
InputLocation location = build.getLocation("sourceDirectory");
boolean isExplicit = location != null && !isSuperPomLocation(location);
Where to Implement Validation
| Option | Location | Pros | Cons |
|---|---|---|---|
| ModelValidator (Recommended) | validateEffectiveModel() | Line numbers, early, standard | Values not fully resolved |
| DefaultProjectBuilder | initProject() | All values resolved | Later, less standard |
| Lifecycle phase | validate | - | Too late |
Related Code Locations
impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.javaimpl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.javaimpl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.javaimpl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.0.0.xml(Super POM 4.0)impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml(Super POM 4.1)
Open Questions
- Should we validate at
validateRawModel()orvalidateEffectiveModel()? - How to reliably detect "explicit" configuration (InputLocation vs value comparison)?
- Should the filesystem warning (R4) be optional/configurable?