diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FileElementRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FileElementRenderer.java
index bd012dd50a71e4f92f910de68f9329e941b9c833..1b20928c705cebe281b836ba4da4de9b9417691c 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FileElementRenderer.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FileElementRenderer.java
@@ -102,6 +102,9 @@ public class FileElementRenderer extends DefaultComponentRenderer {
 				 sb.append(id); // name for form labeling
 				 sb.append("\" id=\"");
 				 sb.append(id); // id to make dirty button work
+				 if (fileElem.getMaxUploadSizeKB() != FileElement.UPLOAD_UNLIMITED) {
+					 sb.append("\" data-max-size=\"").append(fileElem.getMaxUploadSizeKB() * 1024l);
+				 }
 				 sb.append("\" class='form-control o_realchooser ").append(" o_chooser_with_delete", showDeleteButton).append("' ");
 				 // Add on* event handlers
 				 StringBuilder eventHandlers = FormJSHelper.getRawJSFor(fileElem.getRootForm(), id, fileElem.getAction());
diff --git a/src/main/webapp/static/js/functions.js b/src/main/webapp/static/js/functions.js
index 66fa538d7121bf1378a576ed140971c41ef7acac..e977bf51b40a9f2dffa4063a3db5fa970860db6c 100644
--- a/src/main/webapp/static/js/functions.js
+++ b/src/main/webapp/static/js/functions.js
@@ -796,12 +796,32 @@ function o_openPopUp(url, windowname, width, height, menubar) {
 }
 
 function b_handleFileUploadFormChange(fileInputElement, fakeInputElement, saveButton) {
+
+	fileInputElement.setCustomValidity('');
+
+	if (fileInputElement.hasAttribute('data-max-size')) {
+		// check if the file selected does satisfy the max-size constraint
+		var maxSize = fileInputElement.getAttribute('data-max-size');
+		if (maxSize) {
+			var fileSize = formInputFileSize(fileInputElement);
+			if (fileSize > maxSize) {
+				// show a validation error message, reset the fileInputElement and stop processing
+				// to prevent unneeded uploads of potentially really big files
+				var trans = jQuery(document).ooTranslator().getTranslator(o_info.locale, 'org.olat.modules.forms.ui');
+				var msgLimitExceeded = trans.translate('file.upload.error.limit.exeeded');
+				var msgUploadLimit = trans.translate('file.upload.limit');
+				fileInputElement.setCustomValidity(msgLimitExceeded
+						+ " (" + msgUploadLimit + ": " + (maxSize / 1024 / 1024 / 1024).toFixed(1) + " GB)");
+			}
+		}
+	}
+
 	// file upload forms are rendered transparent and have a fake input field that is rendered.
 	// on change events of the real input field this method is triggered to display the file 
 	// path in the fake input field. See the code for more info on this
 	var fileName = fileInputElement.value;
 	// remove unix path
-	slashPos = fileName.lastIndexOf('/');
+	var slashPos = fileName.lastIndexOf('/');
 	if (slashPos != -1) {
 		fileName=fileName.substring(slashPos + 1); 
 	}
@@ -825,6 +845,30 @@ function b_handleFileUploadFormChange(fileInputElement, fakeInputElement, saveBu
 	}
 }
 
+// Return the file size of the selected file in bytes. Returns -1 when API is not working or
+// no file was selected.
+function formInputFileSize(fileInputElement) {
+	try {
+		if (!window.FileReader) {
+			// file API is not supported do proceed as if the file satisfies the constraint
+			return -1;
+		}
+		if (!fileInputElement || !fileInputElement.files) {
+			// missing input element parameter or element is not a file input
+			return -1;
+		}
+		var file = fileInputElement.files[0];
+		if (!file) {
+			// no file selected!
+			return -1;
+		}
+		return file.size;
+	} catch (e) {
+		o_logerr('form input file size check failed: ' + e);
+	}
+	return -1;
+}
+
 // goto node must be in global scope to support content that has been opened in a new window 
 // with the clone controller - real implementation is moved to course run scope o_activateCourseNode()
 function gotonode(nodeid) {
@@ -1166,13 +1210,29 @@ function o_ffEvent(formNam, dispIdField, dispId, eventIdField, eventInt){
 	eventIdEl.value=eventInt;
 	// manually execute onsubmit method - calling submit itself does not trigger onsubmit event!
 	var form = jQuery('#' + formNam);
-	var enctype = form.attr('enctype');
-	if(enctype && enctype.indexOf("multipart") == 0) {
-		o_XHRSubmitMultipart(formNam);
-	} else if (document.forms[formNam].onsubmit()) {
-		document.forms[formNam].submit();
+	var formValid = true;
+	jQuery('#' + formNam + ' input')
+	    .filter(function(index, element) {return !element.checkValidity()})
+	    .each(function(index, element) {
+            var valErrorElementId = element.getAttribute('id') + "_validation_error";
+            var valErrorElement = document.getElementById(valErrorElementId);
+            if (!valErrorElement) {
+                    valErrorElement = document.createElement('div');
+                    valErrorElement.setAttribute('class','o_error');
+                    valErrorElement.setAttribute('id', valErrorElementId);
+                    element.parentNode.parentNode.appendChild(valErrorElement);
+            }
+            valErrorElement.innerHTML = element.validationMessage;
+			formValid = false;
+	    });
+	if (formValid) {
+		var enctype = form.attr('enctype');
+		if(enctype && enctype.indexOf("multipart") == 0) {
+			o_XHRSubmitMultipart(formNam);
+		} else if (document.forms[formNam].onsubmit()) {
+			document.forms[formNam].submit();
+		}
 	}
-	
 	dispIdEl.value = defDispId;
 	eventIdEl.value = defEventId;
 }